Commit 9e8451a1 by Will Daly

Add time fields to the OA editing views

Use resolve dates for default date values
parent dfb87ab4
......@@ -30,15 +30,59 @@
</li>
<li class="openassessment_date_editor field comp-setting-entry">
<div class="wrapper-comp-setting">
<label for="openassessment_submission_start_editor" class="setting-label">{% trans "Response Submission Start Date"%} </label>
<input type="datetime-local" class="input setting-input" id="openassessment_submission_start_editor" value="{{ submission_start }}">
<label
for="openassessment_submission_start_date"
class="setting-label">
{% trans "Submission Start Date" %}
</label>
<input
type="text"
class="input setting-input"
id="openassessment_submission_start_date"
value="{{ submission_start|date:"y-m-d" }}"
>
</div>
<div class="wrapper-comp-setting">
<label
for="openassessment_submission_start_time"
class="setting-label">
{% trans "Submission Start Time" %}
</label>
<input
type="text"
class="input setting-input"
id="openassessment_submission_start_time"
value="{{ submission_start|date:"H:i" }}"
>
</div>
<p class="setting-help">{% trans "The date at which submissions will first be accepted." %}</p>
</li>
<li class="openassessment_date_editor field comp-setting-entry">
<div class="wrapper-comp-setting">
<label for="openassessment_submission_due_editor" class="setting-label">{% trans "Response Submission Due Date" %}</label>
<input type="datetime-local" class="input setting-input" id="openassessment_submission_due_editor" value="{{ submission_due }}">
<label
for="openassessment_submission_due_date"
class="setting-label">
{% trans "Submission Due Date" %}
</label>
<input
type="text"
class="input setting-input"
id="openassessment_submission_due_date"
value="{{ submission_due|date:"y-m-d" }}"
>
</div>
<div class="wrapper-comp-setting">
<label
for="openassessment_submission_due_time"
class="setting-label">
{% trans "Submission Due Time" %}
</label>
<input
type="text"
class="input setting-input"
id="openassessment_submission_due_time"
value="{{ submission_due|date:"H:i" }}"
>
</div>
<p class="setting-help">{% trans "The date at which submissions will stop being accepted." %}</p>
</li>
......
{% load i18n %}
{% load tz %}
{% spaceless %}
<li class="openassessment_assessment_module_settings_editor" id="oa_peer_assessment_editor">
<div class = "drag-handle action"></div>
......@@ -33,16 +34,44 @@
<li class="field comp-setting-entry">
<div class="wrapper-comp-setting">
<label for="peer_assessment_start_date" class="setting-label">{% trans "Start Date" %}</label>
<input id="peer_assessment_start_date" type="datetime-local" class="input setting-input" value="{{ assessments.peer_assessment.start }}">
<input
id="peer_assessment_start_date"
type="text"
class="input setting-input"
value="{{ assessments.peer_assessment.start|utc|date:"Y-m-d" }}"
>
</div>
<p class="setting-help">{% trans "If desired, specify a start date for the peer assessment period. If no date is specified, peer assessment can begin when submissions begin."%}</p>
<div class="wrapper-comp-setting">
<label for="peer_assessment_start_time" class="setting-label">{% trans "Start Time" %}</label>
<input
id="peer_assessment_start_time"
type="text"
class="input setting-input"
value="{{ assessments.peer_assessment.start|utc|date:"H:i" }}"
>
</div>
<p class="setting-help">{% trans "Specify a start date for the peer assessment period." %}</p>
</li>
<li class="field comp-setting-entry">
<div class="wrapper-comp-setting">
<label for="peer_assessment_due_date" class="setting-label">{% trans "Due Date" %}</label>
<input id="peer_assessment_due_date" type="datetime-local" class="input setting-input" value="{{ assessments.peer_assessment.due }}">
<input
id="peer_assessment_due_date"
type="text"
class="input setting-input"
value="{{ assessments.peer_assessment.due|utc|date:"Y-m-d" }}"
>
</div>
<div class="wrapper-comp-setting">
<label for="peer_assessment_due_time" class="setting-label">{% trans "Due Time" %}</label>
<input
id="peer_assessment_due_time"
type="text"
class="input setting-input"
value="{{ assessments.peer_assessment.due|utc|date:"H:i" }}"
>
</div>
<p class="setting-help">{% trans "If desired, specify a due date for the peer assessment period. If no date is specified, peer assessment can run as long as the problem is open."%}</p>
<p class="setting-help">{% trans "Specify a due date for the peer assessment period." %}</p>
</li>
</ul>
</div>
......
{% load i18n %}
{% load tz %}
{% spaceless %}
<li class="openassessment_assessment_module_settings_editor" id="oa_self_assessment_editor">
<div class = "drag-handle action"></div>
......@@ -19,16 +20,44 @@
<li class="field comp-setting-entry">
<div class="wrapper-comp-setting">
<label for="self_assessment_start_date" class="setting-label">{% trans "Start Date" %}</label>
<input id="self_assessment_start_date" type="datetime-local" class="input setting-input" value="{{ assessments.self_assessment.start }}">
<input
id="self_assessment_start_date"
type="text"
class="input setting-input"
value="{{ assessments.self_assessment.start|utc|date:"Y-m-d" }}"
>
</div>
<p class="setting-help">{% trans "If desired, specify a start date for the self assessment period. If no date is specified, self assessment can begin when submissions begin."%}</p>
<div class="wrapper-comp-setting">
<label for="self_assessment_start_time" class="setting-label">{% trans "Start Time" %}</label>
<input
id="self_assessment_start_time"
type="text"
class="input setting-input"
value="{{ assessments.self_assessment.start|utc|date:"H:i" }}"
>
</div>
<p class="setting-help">{% trans "Specify a start date for the self assessment period." %}</p>
</li>
<li class="field comp-setting-entry">
<div class="wrapper-comp-setting">
<label for="self_assessment_due_date" class="setting-label">{% trans "Due Date" %}</label>
<input id="self_assessment_due_date" type="datetime-local" class="input setting-input" value="{{ assessments.self_assessment.due }}">
<input
id="self_assessment_due_date"
type="text"
class="input setting-input"
value="{{ assessments.self_assessment.due|utc|date:"Y-m-d" }}"
>
</div>
<div class="wrapper-comp-setting">
<label for="self_assessment_due_time" class="setting-label">{% trans "Due Time" %}</label>
<input
id="self_assessment_due_time"
type="text"
class="input setting-input"
value="{{ assessments.self_assessment.due|utc|date:"H:i" }}"
>
</div>
<p class="setting-help">{% trans "If desired, specify a due date for the self assessment period. If no date is specified, self assessment can run as long as the problem is open."%}</p>
<p class="setting-help">{% trans "Specify a due date for the self assessment period." %}</p>
</li>
</ul>
</div>
......
......@@ -437,7 +437,8 @@
],
"assessments": {
"peer_assessment": {
"start": "2014-10-04T00:00:00",
"start": "",
"due": "",
"must_grade": 5,
"must_be_graded_by": 3
},
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -36,8 +36,8 @@ describe("OpenAssessment edit assessment views", function() {
it("Loads a description", function() {
view.mustGradeNum(1);
view.mustBeGradedByNum(2);
view.startDatetime("2014-01-01T00:00");
view.dueDatetime("2014-03-04T00:00");
view.startDatetime("2014-01-01", "00:00");
view.dueDatetime("2014-03-04", "00:00");
expect(view.description()).toEqual({
must_grade: 1,
must_be_graded_by: 2,
......@@ -65,8 +65,8 @@ describe("OpenAssessment edit assessment views", function() {
it("Enables and disables", function() { testEnableAndDisable(view); });
it("Loads a description", function() {
view.startDatetime("2014-01-01T00:00");
view.dueDatetime("2014-03-04T00:00");
view.startDatetime("2014-01-01", "00:00");
view.dueDatetime("2014-03-04", "00:00");
expect(view.description()).toEqual({
start: "2014-01-01T00:00",
due: "2014-03-04T00:00"
......@@ -74,8 +74,8 @@ describe("OpenAssessment edit assessment views", function() {
});
it("Handles default dates", function() {
view.startDatetime("");
view.dueDatetime("");
view.startDatetime("", "");
view.dueDatetime("", "");
expect(view.description().start).toBe(null);
expect(view.description().due).toBe(null);
});
......
......@@ -47,17 +47,17 @@ describe("OpenAssessment.EditSettingsView", function() {
});
it("sets and loads the submission start/due dates", function() {
view.submissionStart("");
view.submissionStart("", "");
expect(view.submissionStart()).toBe(null);
view.submissionStart("2014-04-01T00:00.0000Z");
expect(view.submissionStart()).toEqual("2014-04-01T00:00.0000Z");
view.submissionStart("2014-04-01", "00:00");
expect(view.submissionStart()).toEqual("2014-04-01T00:00");
view.submissionDue("");
view.submissionDue("", "");
expect(view.submissionDue()).toBe(null);
view.submissionDue("2014-05-02T00:00.0000Z");
expect(view.submissionDue()).toEqual("2014-05-02T00:00.0000Z");
view.submissionDue("2014-05-02", "00:00");
expect(view.submissionDue()).toEqual("2014-05-02T00:00");
});
it("sets and loads the image enabled state", function() {
......
/**
Show and hide elements based on a checkbox.
Args:
element (DOM element): The parent element, used to scope the selectors.
hiddenSelector (string): The CSS selector string for elements
to show when the checkbox is in the "off" state.
shownSelector (string): The CSS selector string for elements
to show when the checkbox is in the "on" state.
**/
OpenAssessment.ToggleControl = function(element, hiddenSelector, shownSelector) {
this.element = element;
this.hiddenSelector = hiddenSelector;
this.shownSelector = shownSelector;
};
OpenAssessment.ToggleControl.prototype = {
/**
Install the event handler for the checkbox,
passing in the toggle control object as the event data.
Args:
checkboxSelector (string): The CSS selector string for the checkbox.
Returns:
OpenAssessment.ToggleControl
**/
install: function(checkboxSelector) {
$(checkboxSelector, this.element).change(
this, function(event) {
var control = event.data;
if (this.checked) { control.show(); }
else { control.hide(); }
}
);
return this;
},
show: function() {
$(this.hiddenSelector, this.element).addClass('is--hidden');
$(this.shownSelector, this.element).removeClass('is--hidden');
},
hide: function() {
$(this.hiddenSelector, this.element).removeClass('is--hidden');
$(this.shownSelector, this.element).addClass('is--hidden');
}
};
/**
Interface for editing peer assessment settings.
Args:
......@@ -62,11 +12,25 @@ OpenAssessment.EditPeerAssessmentView = function(element) {
this.element = element;
this.name = "peer-assessment";
// Configure the toggle checkbox to enable/disable this assessment
new OpenAssessment.ToggleControl(
this.element,
"#peer_assessment_description_closed",
"#peer_assessment_settings_editor"
).install("#include_peer_assessment");
// Configure the date and time fields
this.startDatetimeControl = new OpenAssessment.DatetimeControl(
this.element,
"#peer_assessment_start_date",
"#peer_assessment_start_time"
).install();
this.dueDatetimeControl = new OpenAssessment.DatetimeControl(
this.element,
"#peer_assessment_due_date",
"#peer_assessment_due_time"
).install();
};
OpenAssessment.EditPeerAssessmentView.prototype = {
......@@ -141,28 +105,28 @@ OpenAssessment.EditPeerAssessmentView.prototype = {
Get or set the start date and time of the assessment.
Args:
datetime (string, optional): If provided, set the datetime to this value.
dateString (string, optional): If provided, set the date (YY-MM-DD).
timeString (string, optional): If provided, set the time (HH:MM, 24-hour clock).
Returns:
string (ISO-formatted UTC datetime)
**/
startDatetime: function(datetime) {
var sel = $("#peer_assessment_start_date", this.element);
return OpenAssessment.Fields.datetimeField(sel, datetime);
startDatetime: function(dateString, timeString) {
return this.startDatetimeControl.datetime(dateString, timeString);
},
/**
Get or set the due date and time of the assessment.
Args:
datetime (string, optional): If provided, set the datetime to this value.
dateString (string, optional): If provided, set the date (YY-MM-DD).
timeString (string, optional): If provided, set the time (HH:MM, 24-hour clock).
Returns:
string (ISO-formatted UTC datetime)
**/
dueDatetime: function(datetime) {
var sel = $("#peer_assessment_due_date", this.element);
return OpenAssessment.Fields.datetimeField(sel, datetime);
dueDatetime: function(dateString, timeString) {
return this.dueDatetimeControl.datetime(dateString, timeString);
},
/**
......@@ -191,11 +155,25 @@ OpenAssessment.EditSelfAssessmentView = function(element) {
this.element = element;
this.name = "self-assessment";
// Configure the toggle checkbox to enable/disable this assessment
new OpenAssessment.ToggleControl(
this.element,
"#self_assessment_description_closed",
"#self_assessment_settings_editor"
).install("#include_self_assessment");
// Configure the date and time fields
this.startDatetimeControl = new OpenAssessment.DatetimeControl(
this.element,
"#self_assessment_start_date",
"#self_assessment_start_time"
).install();
this.dueDatetimeControl = new OpenAssessment.DatetimeControl(
this.element,
"#self_assessment_due_date",
"#self_assessment_due_time"
).install();
};
OpenAssessment.EditSelfAssessmentView.prototype = {
......@@ -239,28 +217,28 @@ OpenAssessment.EditSelfAssessmentView.prototype = {
Get or set the start date and time of the assessment.
Args:
datetime (string, optional): If provided, set the datetime to this value.
dateString (string, optional): If provided, set the date (YY-MM-DD).
timeString (string, optional): If provided, set the time (HH:MM, 24-hour clock).
Returns:
string (ISO-formatted UTC datetime)
**/
startDatetime: function(datetime) {
var sel = $("#self_assessment_start_date", this.element);
return OpenAssessment.Fields.datetimeField(sel, datetime);
startDatetime: function(dateString, timeString) {
return this.startDatetimeControl.datetime(dateString, timeString);
},
/**
Get or set the due date and time of the assessment.
Args:
datetime (string, optional): If provided, set the datetime to this value.
dateString (string, optional): If provided, set the date (YY-MM-DD).
timeString (string, optional): If provided, set the time (HH:MM, 24-hour clock).
Returns:
string (ISO-formatted UTC datetime)
**/
dueDatetime: function(datetime) {
var sel = $("#self_assessment_due_date", this.element);
return OpenAssessment.Fields.datetimeField(sel, datetime);
dueDatetime: function(dateString, timeString) {
return this.dueDatetimeControl.datetime(dateString, timeString);
},
/**
......
......@@ -7,12 +7,6 @@ OpenAssessment.Fields = {
return sel.val();
},
datetimeField: function(sel, value) {
if (typeof(value) !== "undefined") { sel.val(value); }
var fieldValue = sel.val();
return (fieldValue !== "") ? fieldValue : null;
},
intField: function(sel, value) {
if (typeof(value) !== "undefined") { sel.val(value); }
return parseInt(sel.val(), 10);
......@@ -23,3 +17,113 @@ OpenAssessment.Fields = {
return sel.prop("checked");
},
};
/**
Show and hide elements based on a checkbox.
Args:
element (DOM element): The parent element, used to scope the selectors.
hiddenSelector (string): The CSS selector string for elements
to show when the checkbox is in the "off" state.
shownSelector (string): The CSS selector string for elements
to show when the checkbox is in the "on" state.
**/
OpenAssessment.ToggleControl = function(element, hiddenSelector, shownSelector) {
this.element = element;
this.hiddenSelector = hiddenSelector;
this.shownSelector = shownSelector;
};
OpenAssessment.ToggleControl.prototype = {
/**
Install the event handler for the checkbox,
passing in the toggle control object as the event data.
Args:
checkboxSelector (string): The CSS selector string for the checkbox.
Returns:
OpenAssessment.ToggleControl
**/
install: function(checkboxSelector) {
$(checkboxSelector, this.element).change(
this, function(event) {
var control = event.data;
if (this.checked) { control.show(); }
else { control.hide(); }
}
);
return this;
},
show: function() {
$(this.hiddenSelector, this.element).addClass('is--hidden');
$(this.shownSelector, this.element).removeClass('is--hidden');
},
hide: function() {
$(this.hiddenSelector, this.element).removeClass('is--hidden');
$(this.shownSelector, this.element).addClass('is--hidden');
}
};
/**
Date and time input fields.
Args:
element (DOM element): The parent element of the control inputs.
datePicker (string): The CSS selector for the date input field.
timePicker (string): The CSS selector for the time input field.
**/
OpenAssessment.DatetimeControl = function(element, datePicker, timePicker) {
this.element = element;
this.datePicker = datePicker;
this.timePicker = timePicker;
};
OpenAssessment.DatetimeControl.prototype = {
/**
Configure the date and time picker inputs.
Returns:
OpenAssessment.DatetimeControl
**/
install: function() {
var dateString = $(this.datePicker, this.element).val();
$(this.datePicker, this.element).datepicker({ showButtonPanel: true })
.datepicker("option", "dateFormat", "yy-mm-dd")
.datepicker("setDate", dateString);
$(this.timePicker, this.element).timepicker({
timeFormat: 'H:i',
step: 60
});
return this;
},
/**
Get or set the date and time.
Args:
dateString (string, optional): If provided, set the date (YY-MM-DD).
timeString (string, optional): If provided, set the time (HH:MM, 24-hour clock).
Returns:
ISO-formatted datetime string.
**/
datetime: function(dateString, timeString) {
var datePickerSel = $(this.datePicker, this.element);
var timePickerSel = $(this.timePicker, this.element);
if (typeof(dateString) !== "undefined") { datePickerSel.datepicker("setDate", dateString); }
if (typeof(timeString) !== "undefined") { timePickerSel.val(timeString); }
if (datePickerSel.val() === "" && timePickerSel.val() === "") {
return null;
}
return datePickerSel.val() + "T" + timePickerSel.val();
}
};
\ No newline at end of file
......@@ -13,6 +13,19 @@ OpenAssessment.EditSettingsView = function(element, assessmentViews) {
this.settingsElement = element;
this.assessmentsElement = $(element).siblings('#openassessment_assessment_module_settings_editors').get(0);
this.assessmentViews = assessmentViews;
// Configure the date and time fields
this.startDatetimeControl = new OpenAssessment.DatetimeControl(
this.element,
"#openassessment_submission_start_date",
"#openassessment_submission_start_time"
).install();
this.dueDatetimeControl = new OpenAssessment.DatetimeControl(
this.element,
"#openassessment_submission_due_date",
"#openassessment_submission_due_time"
).install();
};
......@@ -37,30 +50,30 @@ OpenAssessment.EditSettingsView.prototype = {
Get or set the submission start date.
Args:
datetime (string, optional): If provided, set the datetime.
dateString (string, optional): If provided, set the date (YY-MM-DD).
timeString (string, optional): If provided, set the time (HH:MM, 24-hour clock).
Returns:
string (ISO-format UTC datetime)
**/
submissionStart: function(datetime) {
var sel = $("#openassessment_submission_start_editor", this.settingsElement);
return OpenAssessment.Fields.datetimeField(sel, datetime);
submissionStart: function(dateString, timeString) {
return this.startDatetimeControl.datetime(dateString, timeString);
},
/**
Get or set the submission end date.
Args:
datetime (string, optional): If provided, set the datetime.
dateString (string, optional): If provided, set the date (YY-MM-DD).
timeString (string, optional): If provided, set the time (HH:MM, 24-hour clock).
Returns:
string (ISO-format UTC datetime)
**/
submissionDue: function(datetime) {
var sel = $("#openassessment_submission_start_editor", this.settingsElement);
return OpenAssessment.Fields.datetimeField(sel, datetime);
submissionDue: function(dateString, timeString) {
return this.dueDatetimeControl.datetime(dateString, timeString);
},
/**
......
......@@ -7,6 +7,8 @@ import copy
import logging
from django.template.loader import get_template
from django.utils.translation import ugettext as _
from dateutil.parser import parse as parse_date
import pytz
from voluptuous import MultipleInvalid
from xblock.core import XBlock
from xblock.fragment import Fragment
......@@ -14,6 +16,7 @@ from openassessment.xblock import xml
from openassessment.xblock.validation import validator
from openassessment.xblock.data_conversion import create_rubric_dict
from openassessment.xblock.schema import EDITOR_UPDATE_SCHEMA
from openassessment.xblock.resolve_dates import resolve_dates
logger = logging.getLogger(__name__)
......@@ -62,34 +65,27 @@ class StudioMixin(object):
'assessments (dict)
"""
# Copies the rubric assessments so that we can change student
# training examples from dict -> str without negatively modifying
# the openassessmentblock definition.
# Django Templates cannot handle dict keys with dashes, so we'll convert
# the dashes to underscores.
# used_assessments (and its unused counterpart) are lists intended to indicate
# the order that settings editors should be rendered. Using lists allows a set order
# which django can easily convert into template names.
used_assessments = []
assessments = {}
for assessment in self.rubric_assessments:
name = assessment['name'].replace('-', '_')
used_assessments.append(name)
assessments[name] = copy.deepcopy(assessment)
# In the authoring GUI, date and time fields should never be null.
# Therefore, we need to resolve all "default" dates to datetime objects
# before displaying them in the editor.
__, __, date_ranges = resolve_dates(
self.start, self.due,
[(self.submission_start, self.submission_due)] +
[(asmnt.get('start'), asmnt.get('due')) for asmnt in self.valid_assessments]
)
unused_assessments = {'student_training', 'peer_assessment', 'self_assessment', 'example_based_assessment'}
unused_assessments = unused_assessments - set(used_assessments)
submission_start, submission_due = date_ranges[0]
assessments = self._assessments_editor_context(date_ranges[1:])
student_training_module = self.get_assessment_module(
'student-training'
)
used_assessments = assessments.keys()
all_assessments = set(['student_training', 'peer_assessment', 'self_assessment', 'example_based_assessment'])
unused_assessments = all_assessments - set(used_assessments)
student_training_module = self.get_assessment_module('student-training')
if student_training_module:
student_training_module = copy.deepcopy(student_training_module)
try:
examples = xml.serialize_examples_to_xml_str(
student_training_module
)
examples = xml.serialize_examples_to_xml_str(student_training_module )
student_training_module["examples"] = examples
assessments['training'] = student_training_module
# We do not expect serialization to raise an exception, but if it does,
......@@ -97,8 +93,8 @@ class StudioMixin(object):
except:
logger.exception("An error occurred while serializing the XBlock")
submission_due = self.submission_due if self.submission_due else ''
submission_start = self.submission_start if self.submission_start else ''
submission_due = parse_date(self.submission_due).replace(tzinfo=pytz.utc) if self.submission_due else ''
submission_start = parse_date(self.submission_start).replace(tzinfo=pytz.utc) if self.submission_start else ''
# Every rubric requires one criterion. If there is no criteria
# configured for the XBlock, return one empty default criterion, with
......@@ -192,3 +188,27 @@ class StudioMixin(object):
'success': True, 'msg': u'',
'is_released': self.is_released()
}
def _assessments_editor_context(self, assessment_dates):
"""
Transform the rubric assessments list into the context
we will pass to the Django template.
Args:
assessment_dates: List of assessment date ranges (tuples of start/end datetimes).
Returns:
dict
"""
assessments = {}
for asmnt, date_range in zip(self.rubric_assessments, assessment_dates):
# Django Templates cannot handle dict keys with dashes, so we'll convert
# the dashes to underscores.
name = asmnt['name']
template_name = name.replace('-', '_')
assessments[template_name] = copy.deepcopy(asmnt)
assessments[template_name]['start'] = date_range[0]
assessments[template_name]['due'] = date_range[1]
return assessments
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