Commit 4c473af4 by Will Daly

Make start/end date fields store datetime objects internally

parent 74af9d4a
"""An XBlock where students can read a question and compose their response"""
import datetime as dt
import json
import pkg_resources
import pytz
......@@ -11,7 +12,7 @@ from django.template.loader import get_template
from webob import Response
from xblock.core import XBlock
from xblock.fields import List, Scope, String, Boolean
from xblock.fields import DateTime, List, Scope, String, Boolean
from xblock.fragment import Fragment
from openassessment.xblock.grade_mixin import GradeMixin
......@@ -168,12 +169,12 @@ class OpenAssessmentBlock(
WorkflowMixin):
"""Displays a question and gives an area where students can compose a response."""
start = String(
start = DateTime(
default=None, scope=Scope.settings,
help="ISO-8601 formatted string representing the start date of this assignment."
)
due = String(
due = DateTime(
default=None, scope=Scope.settings,
help="ISO-8601 formatted string representing the due date of this assignment."
)
......@@ -379,13 +380,11 @@ class OpenAssessmentBlock(
context_dict["xblock_trace"] = self.get_xblock_trace()
if self.start:
start = dateutil.parser.parse(self.start)
context_dict["formatted_start_date"] = start.strftime("%A, %B %d, %Y")
context_dict["formatted_start_datetime"] = start.strftime("%A, %B %d, %Y %X")
context_dict["formatted_start_date"] = self.start.strftime("%A, %B %d, %Y")
context_dict["formatted_start_datetime"] = self.start.strftime("%A, %B %d, %Y %X")
if self.due:
due = dateutil.parser.parse(self.due)
context_dict["formatted_due_date"] = due.strftime("%A, %B %d, %Y")
context_dict["formatted_due_datetime"] = due.strftime("%A, %B %d, %Y %X")
context_dict["formatted_due_date"] = self.due.strftime("%A, %B %d, %Y")
context_dict["formatted_due_datetime"] = self.due.strftime("%A, %B %d, %Y %X")
template = get_template(path)
context = Context(context_dict)
......@@ -503,4 +502,4 @@ class OpenAssessmentBlock(
"""
for assessment in self.rubric_assessments:
if assessment["name"] == mixin_name:
return assessment
\ No newline at end of file
return assessment
......@@ -25,12 +25,12 @@ DISTANT_PAST = dt.datetime(dt.MINYEAR, 1, 1, tzinfo=pytz.utc)
DISTANT_FUTURE = dt.datetime(dt.MAXYEAR, 1, 1, tzinfo=pytz.utc)
def _parse_date(date_string):
def _parse_date(value):
"""
Parse an ISO formatted datestring into a datetime object with timezone set to UTC.
Args:
date_string (str): The ISO formatted date string.
value (str or datetime): The ISO formatted date string or datetime object.
Returns:
datetime.datetime
......@@ -38,10 +38,17 @@ def _parse_date(date_string):
Raises:
InvalidDateFormat: The date string could not be parsed.
"""
try:
return parse_date(date_string).replace(tzinfo=pytz.utc)
except ValueError:
raise InvalidDateFormat(_("Could not parse date '{date}'").format(date=date_string))
if isinstance(value, dt.datetime):
return value.replace(tzinfo=pytz.utc)
elif isinstance(value, basestring):
try:
return parse_date(value).replace(tzinfo=pytz.utc)
except ValueError:
raise InvalidDateFormat(_("Could not parse date '{date}'").format(date=value))
else:
raise InvalidDateFormat(_("'{date}' must be a date string or datetime").format(date=value))
def resolve_dates(start, end, date_ranges):
......@@ -90,10 +97,10 @@ def resolve_dates(start, end, date_ranges):
Args:
start (str, ISO date format): When the problem opens. A value of None indicates that the problem is always open.
end (str, ISO date format): When the problem closes. A value of None indicates that the problem never closes.
start (str, ISO date format, or datetime): When the problem opens. A value of None indicates that the problem is always open.
end (str, ISO date format, or datetime): When the problem closes. A value of None indicates that the problem never closes.
date_ranges (list of tuples): list of (start, end) ISO date string tuples indicating
the start/end timestamps of each submission/assessment.
the start/end timestamps (date string or datetime) of each submission/assessment.
Returns:
start (datetime): The resolved start date
......
......@@ -63,6 +63,17 @@ class TestOpenAssessment(XBlockHandlerTestCase):
self.assertTrue(submission_response.body.find("openassessment__response"))
self.assertTrue(submission_response.body.find("April"))
@scenario('data/basic_scenario.xml')
def test_formatted_dates(self, xblock):
# Set start/due dates
xblock.start = dt.datetime(2014, 4, 1, 1, 1, 1)
xblock.due = dt.datetime(2014, 5, 1)
resp = xblock.render_peer_assessment({})
self.assertTrue(resp.body.find('Tuesday, April 01, 2014'))
self.assertTrue(resp.body.find('Thursday, May 01, 2014'))
@scenario('data/basic_scenario.xml', user_id='Bob')
def test_default_fields(self, xblock):
......
......@@ -48,7 +48,7 @@ class StudioViewTest(XBlockHandlerTestCase):
# Set the XBlock's release date to the future,
# so we are not restricted in what we can edit
xblock.start = dt.datetime(3000, 1, 1).replace(tzinfo=pytz.utc).isoformat()
xblock.start = dt.datetime(3000, 1, 1).replace(tzinfo=pytz.utc)
request = json.dumps({'xml': self.load_fixture_str('data/updated_block.xml')})
......
......@@ -2,8 +2,11 @@
Tests for serializing to/from XML.
"""
import copy
import datetime as dt
import mock
import lxml.etree as etree
import pytz
import dateutil.parser
from django.test import TestCase
from ddt import ddt, data, file_data, unpack
from openassessment.xblock.openassessmentblock import OpenAssessmentBlock, UI_MODELS
......@@ -12,6 +15,22 @@ from openassessment.xblock.xml import (
)
def _parse_date(value):
"""
Parse test-data date into a TZ-aware datetime.
Args:
value (string or None): The value to parse.
Returns:
TZ-aware datetime or None.
"""
if value is None:
return value
else:
return dateutil.parser.parse(value).replace(tzinfo=pytz.utc)
@ddt
class TestSerializeContent(TestCase):
"""
......@@ -56,12 +75,13 @@ class TestSerializeContent(TestCase):
"""
self.oa_block = mock.MagicMock(OpenAssessmentBlock)
@file_data('data/serialize.json')
def test_serialize(self, data):
self.oa_block.title = data['title']
self.oa_block.prompt = data['prompt']
self.oa_block.start = data['start']
self.oa_block.due = data['due']
self.oa_block.start = _parse_date(data['start'])
self.oa_block.due = _parse_date(data['due'])
self.oa_block.submission_due = data['submission_due']
self.oa_block.rubric_criteria = data['criteria']
self.oa_block.rubric_assessments = data['assessments']
......@@ -278,8 +298,8 @@ class TestUpdateFromXml(TestCase):
self.oa_block.rubric_criteria = dict()
self.oa_block.rubric_assessments = list()
self.oa_block.start = "2000-01-01T00:00:00"
self.oa_block.due = "3000-01-01T00:00:00"
self.oa_block.start = dt.datetime(2000, 1, 1).replace(tzinfo=pytz.utc)
self.oa_block.due = dt.datetime(3000, 1, 1).replace(tzinfo=pytz.utc)
self.oa_block.submission_due = "2000-01-01T00:00:00"
@file_data('data/update_from_xml.json')
......@@ -294,8 +314,8 @@ class TestUpdateFromXml(TestCase):
# Check that the contents of the modified XBlock are correct
self.assertEqual(self.oa_block.title, data['title'])
self.assertEqual(self.oa_block.prompt, data['prompt'])
self.assertEqual(self.oa_block.start, data['start'])
self.assertEqual(self.oa_block.due, data['due'])
self.assertEqual(self.oa_block.start, _parse_date(data['start']))
self.assertEqual(self.oa_block.due, _parse_date(data['due']))
self.assertEqual(self.oa_block.submission_due, data['submission_due'])
self.assertEqual(self.oa_block.rubric_criteria, data['criteria'])
self.assertEqual(self.oa_block.rubric_assessments, data['assessments'])
......@@ -314,4 +334,4 @@ class TestUpdateFromXml(TestCase):
update_from_xml_str(
self.oa_block, "".join(data['xml']),
validator=lambda *args: (False, '')
)
)
\ No newline at end of file
# edX Internal Requirements
git+https://github.com/edx/XBlock.git@c4055bc1#egg=XBlock
git+https://github.com/edx/XBlock.git@c7ef611727d89887a258c5d4774a6efbda36cb13#egg=XBlock
git+https://github.com/ormsbee/xblock-sdk.git@4f62e508#egg=xblock-sdk
# Third Party Requirements
......
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