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