Commit edfa46b2 by Gregory Martin

Merge branch 'master' into yro_release

parents 3f31c1bd 89d31b65
......@@ -71,3 +71,5 @@ test/selenium/screenshots/*
# logging
logs/*.log*
edx-ora2/
......@@ -4,7 +4,7 @@ module.exports = function(config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: 'openassessment/xblock/static/js',
basePath: 'openassessment/xblock/static/',
plugins: [
......@@ -25,37 +25,56 @@ module.exports = function(config) {
// list of files / patterns to load in the browser
files: [
'lib/jquery.min.js',
'lib/*.js',
'src/oa_shared.js',
'src/*.js',
'src/lms/*.js',
'src/studio/*.js',
'spec/test_shared.js',
'spec/*.js',
'spec/lms/*.js',
'spec/studio/*.js',
'js/lib/jquery.min.js',
'js/lib/codemirror.js',
'js/lib/jquery.timepicker.min.js',
'js/lib/jquery-ui-1.10.4.min.js',
'js/lib/underscore-min.js',
'../../../node_modules/requirejs/require.js',
'../../../require-config.js',
{
pattern: '../../../node_modules/moment-timezone/builds/moment-timezone-with-data.min.js',
served: true, included: false
},
{
pattern: '../../../node_modules/moment/min/moment-with-locales.min.js',
served: true, included: false
},
{
pattern: '../../../node_modules/edx-ui-toolkit/src/js/utils/date-utils.js',
served: true, included: false
},
{
pattern: '../../../node_modules/edx-ui-toolkit/src/js/utils/string-utils.js',
served: true, included: false
},
//
'js/src/oa_shared.js',
'js/src/*.js',
'js/src/lms/*.js',
'js/src/studio/*.js',
'js/spec/test_shared.js',
'js/spec/*.js',
'js/spec/lms/*.js',
'js/spec/studio/*.js',
// fixtures
{
pattern: 'fixtures/*.html',
pattern: 'js/fixtures/*.html',
served: true, included: false
}
],
// list of files to exclude
exclude: [
'src/design*.js'
'js/src/design*.js'
],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'src/*.js': 'coverage',
'src/lms/*.js': 'coverage',
'src/studio/*.js': 'coverage'
'js/src/*.js': 'coverage',
'js/src/lms/*.js': 'coverage',
'js/src/studio/*.js': 'coverage'
},
......
......@@ -4,4 +4,4 @@ so import the tasks we want the workers to implement.
"""
# pylint:disable=W0611
from .worker.training import train_classifiers, reschedule_training_tasks
from .worker.grading import grade_essay, reschedule_grading_tasks
\ No newline at end of file
from .worker.grading import grade_essay, reschedule_grading_tasks
......@@ -8,14 +8,14 @@
{% if score %}
<span class="step__label">{% trans "Your Grade" %}: </span>
<span class="grade__value">
<span class="grade__value__title">
{% with points_earned_string=score.points_earned|stringformat:"s" points_possible_string=score.points_possible|stringformat:"s" %}
{% blocktrans with points_earned='<span class="grade__value__earned">'|safe|add:points_earned_string|add:'</span>'|safe points_possible='<span class="grade__value__potential">'|safe|add:points_possible_string|add:'</span>'|safe %}
{{ points_earned }} out of {{ points_possible }}
{% endblocktrans %}
{% endwith %}
<span class="grade__value__title">
{% with points_earned_string=score.points_earned|stringformat:"s" points_possible_string=score.points_possible|stringformat:"s" %}
{% blocktrans with points_earned='<span class="grade__value__earned">'|safe|add:points_earned_string|add:'</span>'|safe points_possible='<span class="grade__value__potential">'|safe|add:points_possible_string|add:'</span>'|safe %}
{{ points_earned }} out of {{ points_possible }}
{% endblocktrans %}
{% endwith %}
</span>
</span>
</span>
{% else %}
<span class="step__label">{% trans "Your Grade" %}</span>
{% endif %}
......
......@@ -47,5 +47,6 @@
{% endif %}
</div>
</div>
<div class="sr reader-feedback" aria-live="polite"></div>
</div>
{% endspaceless %}
......@@ -20,13 +20,17 @@
<span class="step__label">{% trans "Assess Peers" %}</span>
{% if peer_start %}
<span class="step__deadline">
{# Translators: This string displays a date to the user, then tells them the time until that date. Example: "available August 13th, 2014 (in 5 days and 45 minutes)" #}
{% blocktrans with start_date=peer_start|timezone:time_zone|date:"N j, Y H:i e" time_until=peer_start|timeuntil %}available <span class="date">{{ start_date }} (in {{ time_until }})</span>{% endblocktrans %}
{# Translators: This string displays a date to the user, then tells them the time until that date. Example: "available August 13th, 2014 00:00 UTC (in 5 days and 45 minutes)" #}
{% blocktrans with start_date=peer_start|date:"Y-m-d H:i" time_until=peer_start|timeuntil %}
<span class="date ora-datetime" data-datetime="{{ start_date }}" data-string="available {date} (in {{ time_until }})" data-timezone="{{ user_timezone }}" data-language="{{ user_language }}"></span>
{% endblocktrans %}
</span>
{% elif peer_due %}
<span class="step__deadline">
{# Translators: This string displays a date to the user, then tells them the time until that date. Example: "due August 13th, 2014 (in 5 days and 45 minutes)" #}
{% blocktrans with due_date=peer_due|timezone:time_zone|date:"N j, Y H:i e" time_until=peer_due|timeuntil %}due <span class="date">{{ due_date }} (in {{ time_until }})</span>{% endblocktrans %}
{# Translators: This string displays a date to the user, then tells them the time until that date. Example: "due August 13th, 2014 00:00 UTC (in 5 days and 45 minutes)" #}
{% blocktrans with due_date=peer_due|date:"Y-m-d H:i" time_until=peer_due|timeuntil %}
<span class="date ora-datetime" data-datetime="{{ due_date }}" data-string="due {date} (in {{ time_until }})" data-timezone="{{ user_timezone }}" data-language="{{ user_language }}"></span>
{% endblocktrans %}
</span>
{% endif %}
</span>
......
......@@ -13,7 +13,7 @@
{% block title %}
<span class="step__status">
<span class="step__status__value">
<span class="copy">{{review_num}}
<span class="copy">
{% with review_num_string=review_num|stringformat:"s" must_grade_string=must_grade|stringformat:"s" %}
{% blocktrans with review_number='<span class="step__status__number--current">'|safe|add:review_num_string|add:"</span>"|safe num_must_grade='<span class="step__status__value--required">'|safe|add:must_grade_string|add:"</span>"|safe %}
In Progress ({{ review_number }} of {{ num_must_grade }})
......
......@@ -17,16 +17,20 @@
{% if submission_start %}
<span class="step__deadline">
{# Translators: This string displays a date to the user, then tells them the time until that date. Example: "available August 13th, 2014 (in 5 days and 45 minutes)" #}
{% blocktrans with start_date=submission_start|timezone:time_zone|date:"N j, Y H:i e" time_until=submission_start|timeuntil %}available <span class="date">{{ start_date }} (in {{ time_until }})</span>{% endblocktrans %}
{% blocktrans with start_date=submission_start|date:"Y-m-d H:i" time_until=submission_start|timeuntil %}
<span class="date ora-datetime" data-datetime="{{ start_date }}" data-string="available {date} (in {{ time_until }})" data-timezone="{{ user_timezone }}" data-language="{{ user_language }}"></span>
{% endblocktrans %}
</span>
{% elif submission_due %}
<span class="step__deadline">
{# Translators: This string displays a date to the user, then tells them the time until that date. Example: "due August 13th, 2014 (in 5 days and 45 minutes)" #}
{% blocktrans with due_date=submission_due|timezone:time_zone|date:"N j, Y H:i e" time_until=submission_due|timeuntil %}due <span class="date">{{ due_date }} (in {{ time_until }})</span>{% endblocktrans %}
{% blocktrans with due_date=submission_due|date:"Y-m-d H:i" time_until=submission_due|timeuntil %}
<span class="date ora-datetime" data-datetime="{{ due_date }}" data-string="due {date} (in {{ time_until }})" data-timezone="{{ user_timezone }}" data-language="{{ user_language }}"></span>
{% endblocktrans %}
</span>
{% endif %}
</span>
</button>
{% block title %}
<span class="step__status">
<span class="step__status__value">
......@@ -44,7 +48,10 @@
<p>
{% trans "Enter your response to the question." %}
{% if submission_due %}
{% trans "You can save your progress and return to complete your response at any time before the due date" %} (<span class="step__deadline"><span class="date">{{ submission_due|timezone:time_zone|date:"l, N j, Y H:i e" }}</span></span>).
{% trans "You can save your progress and return to complete your response at any time before the due date" %}
(<span class="step__deadline">
<span class="date ora-datetime" data-datetime="{{ submission_due|date:'Y-m-d H:i' }}" data-timezone="{{ user_timezone }}" data-format="longDateTime" data-language="{{ user_language }}"></span>
</span>).
{% else %}
{% trans "You can save your progress and return to complete your response at any time." %}
{% endif %}
......
......@@ -32,12 +32,12 @@
{% trans "Your submission was cancelled. " %}
<p>
{% if workflow_cancellation.cancelled_by %}
{% blocktrans with removed_datetime=workflow_cancellation.cancelled_at|timezone:time_zone|date:"N j, Y H:i e" removed_by_username=workflow_cancellation.cancelled_by %}
{% blocktrans with removed_datetime=workflow_cancellation.cancelled_at|timezone:"UTC"|date:"Y-m-d H:i e" removed_by_username=workflow_cancellation.cancelled_by %}
Your submission has been cancelled by {{ removed_by_username }} on {{ removed_datetime }}
{% endblocktrans %}
{% else %}
{% blocktrans with removed_datetime=workflow_cancellation.cancelled_at|timezone:time_zone|date:"N j, Y H:i e" %}
Your submission was cancelled on {{ removed_datetime }}
{% blocktrans with removed_datetime=workflow_cancellation.cancelled_at|timezone:"UTC"|date:"Y-m-d H:i e" %}
Your submission was cancelled on {{ removed_datetime }}
{% endblocktrans %}
{% endif %}
</p>
......
......@@ -18,12 +18,16 @@
{% if self_start %}
<span class="step__deadline">
{# Translators: This string displays a date to the user, then tells them the time until that date. Example: "available August 13th, 2014 (in 5 days and 45 minutes)" #}
{% blocktrans with start_date=self_start|timezone:time_zone|date:"N j, Y H:i e" time_until=self_start|timeuntil %}available <span class="date">{{ start_date }} (in {{ time_until }})</span>{% endblocktrans %}
{% blocktrans with start_date=self_start|date:"Y-m-d H:i" time_until=self_start|timeuntil %}
<span class="date ora-datetime" data-datetime="{{ start_date }}" data-string="available {date} (in {{ time_until }})" data-timezone="{{ user_timezone }}" data-language="{{ user_language }}"></span>
{% endblocktrans %}
</span>
{% elif self_due %}
<span class="step__deadline">
{# Translators: This string displays a date to the user, then tells them the time until that date. Example: "due August 13th, 2014 (in 5 days and 45 minutes)" #}
{% blocktrans with due_date=self_due|timezone:time_zone|date:"N j, Y H:i e" time_until=self_due|timeuntil %}due <span class="date">{{ due_date }} (in {{ time_until }})</span>{% endblocktrans %}
{% blocktrans with due_date=self_due|date:"Y-m-d H:i" time_until=self_due|timeuntil %}
<span class="date ora-datetime" data-datetime="{{ due_date }}" data-string="due {date} (in {{ time_until }})" data-timezone="{{ user_timezone }}" data-language="{{ user_language }}"></span>
{% endblocktrans %}
</span>
{% endif %}
</span>
......
......@@ -26,11 +26,11 @@
{% if workflow_cancellation %}
<p>
{% if workflow_cancellation.cancelled_by %}
{% blocktrans with removed_by_username=workflow_cancellation.cancelled_by removed_datetime=workflow_cancellation.cancelled_at|timezone:time_zone|date:"F j, Y H:i e" %}
{% blocktrans with removed_by_username=workflow_cancellation.cancelled_by removed_datetime=workflow_cancellation.cancelled_at|timezone:"UTC"|date:"Y-m-d H:i e" %}
Learner submission removed by {{ removed_by_username }} on {{ removed_datetime }}
{% endblocktrans %}
{% else %}
{% blocktrans with removed_datetime=workflow_cancellation.cancelled_at|timezone:time_zone|date:"F j, Y H:i e" %}
{% blocktrans with removed_datetime=workflow_cancellation.cancelled_at|timezone:"UTC"|date:"Y-m-d H:i e" %}
Learner submission removed on {{ removed_datetime }}
{% endblocktrans %}
{% endif %}
......
......@@ -17,12 +17,16 @@
{% if training_start %}
<span class="step__deadline">
{# Translators: This string displays a date to the user, then tells them the time until that date. Example: "available August 13th, 2014 (in 5 days and 45 minutes)" #}
{% blocktrans with start_date=training_start|timezone:time_zone|date:"N j, Y H:i e" time_until=training_start|timeuntil %}available <span class="date">{{ start_date }} (in {{ time_until }})</span>{% endblocktrans %}
{% blocktrans with start_date=training_start|date:"Y-m-d H:i" time_until=training_start|timeuntil %}
<span class="date ora-datetime" data-datetime="{{ start_date }}" data-string="available {date} (in {{ time_until }})" data-timezone="{{ user_timezone }}" data-language="{{ user_language }}"></span>
{% endblocktrans %}
</span>
{% elif training_due %}
<span class="step__deadline">
{# Translators: This string displays a date to the user, then tells them the time until that date. Example: "due August 13th, 2014 (in 5 days and 45 minutes)" #}
{% blocktrans with due_date=training_due|timezone:time_zone|date:"N j, Y H:i e" time_until=training_due|timeuntil %}due <span class="date">{{ due_date }} (in {{ time_until }})</span>{% endblocktrans %}
{% blocktrans with due_date=training_due|date:"Y-m-d H:i" time_until=training_due|timeuntil %}
<span class="date ora-datetime" data-datetime="{{ due_date }}" data-string="due {date} (in {{ time_until }})" data-timezone="{{ user_timezone }}" data-language="{{ user_language }}"></span>
{% endblocktrans %}
</span>
</span>
{% endif %}
......@@ -89,43 +93,41 @@
</button>
</div>
<div class="ui-slidable__content" aria-labelledby="oa_training_{{ xblock_id }}_criterion--{{ criterion.order_num }}" id="oa_training_{{ xblock_id }}_content_criterion--{{ criterion.order_num }}">
<div class="step__message message message--correct is--hidden">
<div id="training__assessment__rubric__correct__message--{{ criterion.order_num }}__{{ xblock_id }}" class="step__message message message--correct is--hidden">
<h5 class="message__title">{% trans "Selected Options Agree" %}</h5>
<div class="message__content">
<p>{% trans "The option you selected is the option that the instructor selected." %}</p>
</div>
</div>
<div class="step__message message message--incorrect is--hidden">
<div id="training__assessment__rubric__incorrect__message--{{ criterion.order_num }}__{{ xblock_id }}" class="step__message message message--incorrect is--hidden">
<h5 class="message__title">{% trans "Selected Options Differ" %}</h5>
<div class="message__content">
<p>{% trans "The option you selected is not the option that the instructor selected." %}</p>
</div>
</div>
<div class="question__answers">
<div role="group" aria-labelledby="training__assessment__rubric__prompt--{{ criterion.order_num }}__{{ xblock_id }}">
{% for option in criterion.options %}
<div class="answer">
<div class="wrapper--input">
<input type="radio"
data-criterion-name="{{ criterion.name }}"
id="training__assessment__rubric__question--{{ criterion.order_num }}__{{ option.order_num }}__{{ xblock_id }}"
class="answer__value rubric_{{ criterion.order_num }}_{{ option.order_num }}"
value="{{ option.name }}"
name="training__assessment__rubric__question--{{ criterion.order_num }}"
aria-describedby="training__assessment__rubric__meta--{{ criterion.order_num }}__{{ option.order_num }}__{{ xblock_id }}"/>
<label for="training__assessment__rubric__question--{{ criterion.order_num }}__{{ option.order_num }}__{{ xblock_id }}"
class="answer__label">{{ option.label }}</label>
</div>
<div class="wrapper--metadata"
id="training__assessment__rubric__meta--{{ criterion.order_num }}__{{ option.order_num }}__{{ xblock_id }}">
<span class="answer__tip">{{ option.explanation }}</span>
<span class="answer__points">{{option.points}} <span class="answer__points__label">{% trans "points" %}</span></span>
</div>
<div class="question__answers" role="group" aria-labelledby="training__assessment__rubric__prompt--{{ criterion.order_num }}__{{ xblock_id }}">
{% for option in criterion.options %}
<div class="answer">
<div class="wrapper--input">
<input type="radio"
data-criterion-name="{{ criterion.name }}"
id="training__assessment__rubric__question--{{ criterion.order_num }}__{{ option.order_num }}__{{ xblock_id }}"
class="answer__value rubric_{{ criterion.order_num }}_{{ option.order_num }}"
value="{{ option.name }}"
name="training__assessment__rubric__question--{{ criterion.order_num }}"
aria-describedby="training__assessment__rubric__meta--{{ criterion.order_num }}__{{ option.order_num }}__{{ xblock_id }}"/>
<label for="training__assessment__rubric__question--{{ criterion.order_num }}__{{ option.order_num }}__{{ xblock_id }}"
class="answer__label">{{ option.label }}</label>
</div>
<div class="wrapper--metadata"
id="training__assessment__rubric__meta--{{ criterion.order_num }}__{{ option.order_num }}__{{ xblock_id }}">
<span class="answer__tip">{{ option.explanation }}</span>
<span class="answer__points">{{option.points}} <span class="answer__points__label">{% trans "points" %}</span></span>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
</div>
</li>
......
"""
The Peer Assessment Mixin for all Peer Functionality.
"""
import logging
from webob import Response
......@@ -10,7 +15,8 @@ from openassessment.assessment.errors import (
from openassessment.workflow.errors import AssessmentWorkflowError
from openassessment.xblock.defaults import DEFAULT_RUBRIC_FEEDBACK_TEXT
from .data_conversion import create_rubric_dict
from .resolve_dates import DISTANT_FUTURE, get_current_time_zone
from .resolve_dates import DISTANT_FUTURE
from .user_data import get_user_preferences
from .data_conversion import clean_criterion_feedback, create_submission_dict, verify_assessment_parameters
logger = logging.getLogger(__name__)
......@@ -161,12 +167,13 @@ class PeerAssessmentMixin(object):
path = 'openassessmentblock/peer/oa_peer_unavailable.html'
finished = False
problem_closed, reason, start_date, due_date = self.is_closed(step="peer-assessment")
user_service = self.runtime.service(self, 'user')
user_preferences = get_user_preferences(self.runtime.service(self, 'user'))
context_dict = {
"rubric_criteria": self.rubric_criteria_with_labels,
"allow_latex": self.allow_latex,
"time_zone": get_current_time_zone(user_service),
"user_timezone": user_preferences['user_timezone'],
"user_language": user_preferences['user_language'],
"xblock_id": self.get_xblock_id(),
}
......
......@@ -24,18 +24,6 @@ DISTANT_PAST = dt.datetime(dt.MINYEAR, 1, 1, tzinfo=pytz.utc)
DISTANT_FUTURE = dt.datetime(dt.MAXYEAR, 1, 1, tzinfo=pytz.utc)
def get_current_time_zone(user_service):
"""
Returns the preferred time zone for the current user, if specified, or UTC if not
:param user_service: XblockUserService
"""
user_preferences = user_service.get_current_user().opt_attrs.get('edx-platform.user_preferences')
if user_preferences is None:
return pytz.utc
return pytz.timezone(user_preferences.get('time_zone', 'utc'))
def _parse_date(value, _):
"""
Parse an ISO formatted datestring into a datetime object with timezone set to UTC.
......
......@@ -6,7 +6,8 @@ from webob import Response
from openassessment.assessment.api import self as self_api
from openassessment.workflow import api as workflow_api
from submissions import api as submission_api
from .resolve_dates import DISTANT_FUTURE, get_current_time_zone
from .resolve_dates import DISTANT_FUTURE
from .user_data import get_user_preferences
from .data_conversion import (clean_criterion_feedback, create_submission_dict,
create_rubric_dict, verify_assessment_parameters)
......@@ -54,12 +55,13 @@ class SelfAssessmentMixin(object):
path = 'openassessmentblock/self/oa_self_unavailable.html'
problem_closed, reason, start_date, due_date = self.is_closed(step="self-assessment")
user_service = self.runtime.service(self, 'user')
user_preferences = get_user_preferences(self.runtime.service(self, 'user'))
context = {
'allow_latex': self.allow_latex,
"xblock_id": self.get_xblock_id(),
'time_zone': get_current_time_zone(user_service)
'user_timezone': user_preferences['user_timezone'],
'user_language': user_preferences['user_language']
}
# We display the due date whether the problem is open or closed.
......
......@@ -24,7 +24,7 @@ from openassessment.assessment.api import self as self_api
from openassessment.assessment.api import ai as ai_api
from openassessment.workflow import api as workflow_api
from openassessment.assessment.api import staff as staff_api
from .resolve_dates import get_current_time_zone
from .user_data import get_user_preferences
logger = logging.getLogger(__name__)
......@@ -319,11 +319,14 @@ class StaffAreaMixin(object):
Returns:
A context dict for rendering a student submission and associated rubric (for staff grading).
"""
user_preferences = get_user_preferences(self.runtime.service(self, 'user')) # localize for staff user
context = {
'submission': create_submission_dict(submission, self.prompts) if submission else None,
'rubric_criteria': copy.deepcopy(self.rubric_criteria_with_labels),
'student_username': student_username,
'time_zone': get_current_time_zone(self.runtime.service(self, 'user')), # localize for staff user
'user_timezone': user_preferences['user_timezone'],
'user_language': user_preferences['user_language']
}
if submission:
......
......@@ -893,7 +893,8 @@
"cancelled_at": "2015-10-01T04:53",
"comments": "Cancelled!"
},
"time_zone": "utc"
"user_timezone": "utc",
"user_language": "en"
},
"output": "oa_staff_cancelled_submission.html"
},
......@@ -1195,5 +1196,22 @@
"student_username": "mock_user_2"
},
"output": "oa_staff_grade_learners_assessment_2.html"
},
{
"template": "openassessmentblock/response/oa_response.html",
"context": {
"saved_response": {
"answer": {
"parts": [
{ "text": "", "prompt": { "description": "Prompt 1" }},
{ "text": "", "prompt": { "description": "Prompt 2" }}
]
}
},
"save_status": "This response has not been saved.",
"submit_enabled": true,
"submission_due": "2020-01-01T00:00"
},
"output": "oa_response_date.html"
}
]
This source diff could not be displayed because it is too large. You can view the blob instead.
/**
Tests for OA dateutil factory.
**/
describe('OpenAssessment.DateTimeFactory', function() {
beforeEach(function() {
// Load the DOM fixture
loadFixtures('oa_response_date.html');
});
describe('apply', function() {
it('has the correct HTML elements', function() {
var timeElement = $('.step__title').get(0);
var datetimeFactory = new OpenAssessment.DateTimeFactory(timeElement);
datetimeFactory.apply();
$('.ora-datetime', timeElement).each(function() {
var self = this;
expect($(self).data('datetime')).toBe('2019-12-31 19:00');
expect($(self).data('string')).toContain('due {date}');
});
});
});
describe('determineContext', function() {
it('generates a context dict', function() {
var timeElement = $('.step__title').get(0);
var datetimeFactory = new OpenAssessment.DateTimeFactory(timeElement);
$('.ora-datetime', timeElement).each(function() {
var self = this;
$(self).attr('data-language', 'en');
$(self).attr('data-timezone', 'America/Los_Angeles');
});
$('.ora-datetime', timeElement).each(function() {
var el = this;
var testContext = datetimeFactory.determineContext($(el));
expect(testContext['datetime']).toBe('2019-12-31 19:00');
expect(testContext['timezone']).toBe('America/Los_Angeles');
expect(testContext['language']).toBe('en');
expect(testContext['format']).toBe('');
})
})
});
describe('determineDateToken', function() {
it('defaults', function() {
var timeElement = $('.step__title').get(0);
var datetimeFactory = new OpenAssessment.DateTimeFactory(timeElement);
$('.ora-datetime', timeElement).each(function() {
var el = this;
var testDateToken = datetimeFactory.determineDateToken($(el));
expect(testDateToken).toBe('date');
});
$('.ora-datetime', timeElement).each(function() {
var self = this;
$(self).attr('data-datetoken', 'TEST');
});
$('.ora-datetime', timeElement).each(function() {
var el = this;
var testDateToken = datetimeFactory.determineDateToken($(el));
expect(testDateToken).toBe('TEST');
})
})
});
describe('isValid', function() {
it('checks a valid variable', function() {
var timeElement = $('.step__title').get(0);
var datetimeFactory = new OpenAssessment.DateTimeFactory(timeElement);
var testDict = {
'Invalid date': false,
'invalid date': true,
'': false,
1: true
};
Object.keys(testDict).forEach(function(key) {
expect(datetimeFactory.isValid(key)).toEqual(testDict[key]);
});
})
});
});
......@@ -466,7 +466,7 @@ describe('OpenAssessment.StaffAreaView', function() {
// Verify that the student view reflects the cancellation
expect($($('.staff-info__student__response p', staffArea.element)[0]).text().trim()).toBe(
'Learner submission removed by staff on October 1, 2015 04:53 UTC'
'Learner submission removed by staff on 2015-10-01 04:53 UTC'
);
expect($($('.staff-info__student__response p', staffArea.element)[1]).text().trim()).toBe(
'Comments: Cancelled!'
......
......@@ -3,5 +3,5 @@ Common test configuration, loaded before any of the spec files.
**/
// Set the fixture path
jasmine.getFixtures().fixturesPath = 'base/fixtures';
jasmine.getFixtures().fixturesPath = 'base/js/fixtures';
......@@ -27,6 +27,7 @@ OpenAssessment.BaseView = function(runtime, element, server, data) {
// Staff-only area with information and tools for managing student submissions
this.staffAreaView = new OpenAssessment.StaffAreaView(this.element, this.server, this);
this.usageID = '';
this.srStatusUpdates = [];
};
if (typeof OpenAssessment.unsavedChanges === 'undefined' || !OpenAssessment.unsavedChanges) {
......@@ -46,6 +47,7 @@ OpenAssessment.BaseView.prototype = {
SLIDABLE_CONTENT_CLASS: "ui-slidable__content",
SLIDABLE_CONTROLS_CLASS: "ui-slidable__control",
SLIDABLE_CONTAINER_CLASS: "ui-slidable__container",
READER_FEEDBACK_CLASS: '.sr.reader-feedback',
/**
* Checks to see if the scrollTo function is available, then scrolls to the
......@@ -68,6 +70,95 @@ OpenAssessment.BaseView.prototype = {
},
/**
* Clear the text in the Aria live region.
*/
srClear: function() {
$(this.READER_FEEDBACK_CLASS).html('');
},
/**
* Add the text messages to the Aria live region.
*
* @param {string[]} texts
*/
srReadTexts: function(texts) {
var $readerFeedbackSelector = $(this.READER_FEEDBACK_CLASS),
htmlFeedback = '';
this.srClear();
$.each(texts, function(ids, value) {
htmlFeedback = htmlFeedback + '<p>' + value + '</p>\n';
});
$readerFeedbackSelector.html(htmlFeedback);
},
/**
* Checks the rendering status of the views that may require Screen Reader Status updates.
*
* The only views that should be added here are those that require Screen Reader updates when moving from one
* step to another.
*
* @returns {boolean} true if any step's view is still loading.
*/
areSRStepsLoading: function() {
return this.responseView.isRendering ||
this.peerView.isRendering ||
this.selfView.isRendering ||
this.gradeView.isRendering ||
this.trainingView.isRendering ||
this.staffView.isRendering;
},
/**
* Updates text in the Aria live region if all sections are rendered and focuses on the specified ID.
*
* @param {String} stepID - The id of the Step being worked on.
* @param {String} usageID - The Usage id of the xBlock.
* @param {boolean} gradeStatus - true if this is a Grade status, false if it is an assessment status.
* @param {Object} currentView - Current active view.
* @param {String} focusID - The ID of the region to focus on.
*/
announceStatusChangeToSRandFocus: function(stepID, usageID, gradeStatus, currentView, focusID) {
var text = this.getStatus(stepID, currentView, gradeStatus);
if (typeof usageID !== 'undefined' &&
$(stepID, currentView.element).hasClass("is--showing") &&
typeof focusID !== 'undefined') {
$(focusID, currentView.element).focus();
this.srStatusUpdates.push(text);
} else if (currentView.announceStatus) {
this.srStatusUpdates.push(text);
}
if (!this.areSRStepsLoading() && this.srStatusUpdates.length > 0) {
this.srReadTexts(this.srStatusUpdates);
this.srStatusUpdates = [];
}
currentView.announceStatus = false;
},
/**
* Retrieves and returns the current status of a given step.
*
* @param {String} stepID - The id of the Step to retrieve status for.
* @param {Object} currentView - The current view.
* @param {boolean} gradeStatus - true if the status to be retrieved is the grade status,
* false if it is the assessment status
* @returns {String} - the current status.
*/
getStatus: function(stepID, currentView, gradeStatus) {
var cssBase = stepID + " .step__header .step__title ";
var cssStringTitle = cssBase + ".step__label";
var cssStringStatus = cssBase + ".step__status";
if (gradeStatus) {
cssStringStatus = cssBase + ".grade__value";
}
return $(cssStringTitle, currentView.element).text().trim() + ' ' +
$(cssStringStatus, currentView.element).text().trim();
},
/**
* Install click handlers to expand/collapse a section.
*
* @param {element} parentElement JQuery selector for the container element.
......@@ -132,6 +223,7 @@ OpenAssessment.BaseView.prototype = {
this.selfView.load(usageID);
this.gradeView.load(usageID);
this.leaderboardView.load(usageID);
/**
this.messageView.load() is intentionally omitted.
Because of the asynchronous loading, there is no way to tell (from the perspective of the
......@@ -190,6 +282,11 @@ OpenAssessment.BaseView.prototype = {
// Send focus to the error message
$(container + " > .message", element).focus();
}
if (message !== null) {
var contentTitle = $(container + " .message__title").text();
this.srReadTexts([contentTitle, message]);
}
},
/**
......@@ -287,4 +384,5 @@ function OpenAssessmentBlock(runtime, element, data) {
var server = new OpenAssessment.Server(runtime, element);
var view = new OpenAssessment.BaseView(runtime, element, server, data);
view.load();
}
/**
*
* A helper function to utilize DateUtils.
**/
OpenAssessment.DateTimeFactory = function(element) {
this.element = element;
};
OpenAssessment.DateTimeFactory.prototype = {
apply: function() {
var dtFactory = this;
$('.ora-datetime', this.element).each(function() {
dtFactory.elementApply($(this));
});
},
determineContext: function(el) {
var context;
context = {
datetime: el.data('datetime'),
timezone: el.data('timezone'),
language: el.data('language'),
format: ''
};
return context;
},
determineDateToken: function(el) {
var dtFactory = this;
var dateToken = 'date';
if (dtFactory.isValid(el.data('datetoken'))) {
dateToken = el.data('datetoken');
}
return dateToken;
},
elementApply: function(el) {
var dtFactory = this;
(function(require) {
require([
'jquery',
'edx-ui-toolkit/js/utils/date-utils',
'edx-ui-toolkit/js/utils/string-utils'
], function($, DateUtils, StringUtils) {
var context;
var localTimeString;
var displayDatetime;
var interpolateDict = {};
if (dtFactory.isValid(el.data('datetime'))) {
context = dtFactory.determineContext(el);
if (dtFactory.isValid(el.data('format'))) {
context.format = DateUtils.dateFormatEnum[el.data('format')];
}
localTimeString = DateUtils.localize(context);
interpolateDict[dtFactory.determineDateToken(el)] = localTimeString;
if (dtFactory.isValid(el.data('string'))) {
displayDatetime = StringUtils.interpolate(
el.data('string'),
interpolateDict
);
} else {
displayDatetime = localTimeString;
}
} else {
displayDatetime = StringUtils.interpolate(
el.data('string'),
interpolateDict
);
}
el.text(displayDatetime);
}
);
}).call(this, require || RequireJS.require);
},
isValid: function(candidateVariable) {
return candidateVariable !== undefined &&
candidateVariable !== '' &&
candidateVariable !== 'Invalid date' &&
candidateVariable !== 'None';
}
};
......@@ -10,6 +10,9 @@ OpenAssessment.GradeView = function(element, server, baseView) {
this.element = element;
this.server = server;
this.baseView = baseView;
this.announceStatus = false;
this.isRendering = false;
this.dateFactory = new OpenAssessment.DateTimeFactory(this.element);
};
OpenAssessment.GradeView.prototype = {
......@@ -20,18 +23,18 @@ OpenAssessment.GradeView.prototype = {
var view = this;
var baseView = this.baseView;
var stepID = ".step--grade";
var focusID = "[id='oa_grade_" + usageID + "']";
view.isRendering = true;
this.server.render('grade').done(
function(html) {
// Load the HTML and install event handlers
$(stepID, view.element).replaceWith(html);
view.server.renderLatex($(stepID, view.element));
view.isRendering = false;
view.installHandlers();
if (typeof usageID !== 'undefined' &&
!$(stepID, view.element).hasClass("is--unfinished") &&
!$(stepID, view.element).hasClass("is--unstarted") &&
!$(stepID, view.element).hasClass("is--waiting--staff")) {
$("[id='oa_grade_" + usageID + "']", view.element).focus();
}
view.baseView.announceStatusChangeToSRandFocus(stepID, usageID, true, view, focusID);
view.dateFactory.apply();
}
).fail(function(errMsg) {
baseView.showLoadError('grade', errMsg);
......
......@@ -14,6 +14,10 @@ OpenAssessment.PeerView = function(element, server, baseView) {
this.server = server;
this.baseView = baseView;
this.rubric = null;
this.isRendering = false;
this.announceStatus = false;
this.dateFactory = new OpenAssessment.DateTimeFactory(this.element);
};
OpenAssessment.PeerView.prototype = {
......@@ -26,15 +30,21 @@ OpenAssessment.PeerView.prototype = {
load: function(usageID) {
var view = this;
var stepID = ".step--peer-assessment";
var focusID = "[id='oa_peer_" + usageID + "']";
view.isRendering = true;
this.server.render('peer_assessment').done(
function(html) {
// Load the HTML and install event handlers
$(stepID, view.element).replaceWith(html);
view.isRendering = false;
view.server.renderLatex($(stepID, view.element));
view.installHandlers(false);
if (typeof usageID !== 'undefined' && $(stepID, view.element).hasClass("is--showing")) {
$("[id='oa_peer_" + usageID + "']", view.element).focus();
}
view.baseView.announceStatusChangeToSRandFocus(stepID, usageID, false, view, focusID);
view.announceStatus = false;
view.dateFactory.apply();
}
).fail(function() {
view.baseView.showLoadError('peer-assessment');
......@@ -52,16 +62,22 @@ OpenAssessment.PeerView.prototype = {
**/
loadContinuedAssessment: function(usageID) {
var view = this;
var stepID = ".step--peer-assessment";
var focusID = "[id='oa_peer_" + usageID + "']";
view.continueAssessmentEnabled(false);
view.isRendering = true;
this.server.renderContinuedPeer().done(
function(html) {
// Load the HTML and install event handlers
$('.step--peer-assessment', view.element).replaceWith(html);
view.server.renderLatex($('.step--peer-assessment', view.element));
view.isRendering = false;
view.installHandlers(true);
if (typeof usageID !== 'undefined') {
$("[id='oa_peer_" + usageID + "']", view.element).focus();
}
view.baseView.announceStatusChangeToSRandFocus(stepID, usageID, false, view, focusID);
}
).fail(function() {
view.baseView.showLoadError('peer-assessment');
......@@ -123,6 +139,9 @@ OpenAssessment.PeerView.prototype = {
// Override default form submission
eventObject.preventDefault();
//Status will change in update announce it to the Screen Reader after Render
view.announceStatus = true;
// Handle the click
if (!isContinuedAssessment) { view.peerAssess(); }
else { view.continuedPeerAssess(); }
......
......@@ -24,6 +24,9 @@ OpenAssessment.ResponseView = function(element, server, fileUploader, baseView,
this.autoSaveTimerId = null;
this.data = data;
this.fileUploaded = false;
this.announceStatus = false;
this.isRendering = false;
this.dateFactory = new OpenAssessment.DateTimeFactory(this.element);
};
OpenAssessment.ResponseView.prototype = {
......@@ -46,6 +49,9 @@ OpenAssessment.ResponseView.prototype = {
load: function(usageID) {
var view = this;
var stepID = '.step--response';
var focusID = "[id='oa_response_" + usageID + "']";
view.isRendering = true;
this.server.render('submission').done(
function(html) {
// Load the HTML and install event handlers
......@@ -53,9 +59,10 @@ OpenAssessment.ResponseView.prototype = {
view.server.renderLatex($(stepID, view.element));
view.installHandlers();
view.setAutoSaveEnabled(true);
if (typeof usageID !== 'undefined' && $(stepID, view.element).hasClass("is--showing")) {
$("[id='oa_response_" + usageID + "']", view.element).focus();
}
view.isRendering = false;
view.baseView.announceStatusChangeToSRandFocus(stepID, usageID, false, view, focusID);
view.announceStatus = false;
view.dateFactory.apply();
}
).fail(function() {
view.baseView.showLoadError('response');
......@@ -344,7 +351,9 @@ OpenAssessment.ResponseView.prototype = {
});
if (currentResponseEqualsSaved) {
view.saveEnabled(false);
view.saveStatus(gettext("This response has been saved but not submitted."));
var msg = gettext("This response has been saved but not submitted.");
view.saveStatus(msg);
view.baseView.srReadTexts([msg]);
}
}).fail(function(errMsg) {
view.saveStatus(gettext('Error'));
......@@ -363,7 +372,6 @@ OpenAssessment.ResponseView.prototype = {
submit: function() {
// Immediately disable the submit button to prevent multiple submission
this.submitEnabled(false);
var view = this;
var baseView = this.baseView;
var fileDefer = $.Deferred();
......@@ -423,9 +431,13 @@ OpenAssessment.ResponseView.prototype = {
moveToNextStep: function() {
var baseView = this.baseView;
var usageID = baseView.getUsageID();
var view = this;
this.load(usageID);
baseView.loadAssessmentModules(usageID);
view.announceStatus = true;
// Disable the "unsaved changes" warning if the user
// tries to navigate to another page.
baseView.unsavedWarningEnabled(false, this.UNSAVED_WARNING_KEY);
......
......@@ -14,6 +14,9 @@ OpenAssessment.SelfView = function(element, server, baseView) {
this.server = server;
this.baseView = baseView;
this.rubric = null;
this.isRendering = false;
this.announceStatus = false;
this.dateFactory = new OpenAssessment.DateTimeFactory(this.element);
};
OpenAssessment.SelfView.prototype = {
......@@ -26,15 +29,19 @@ OpenAssessment.SelfView.prototype = {
load: function(usageID) {
var view = this;
var stepID = '.step--self-assessment';
var focusID = "[id='oa_self_" + usageID + "']";
view.isRendering = true;
this.server.render('self_assessment').done(
function(html) {
// Load the HTML and install event handlers
$(stepID, view.element).replaceWith(html);
view.isRendering = false;
view.server.renderLatex($(stepID, view.element));
view.installHandlers();
if (typeof usageID !== 'undefined' && $(stepID, view.element).hasClass("is--showing")) {
$("[id='oa_self_" + usageID + "']", view.element).focus();
}
view.baseView.announceStatusChangeToSRandFocus(stepID, usageID, false, view, focusID);
view.dateFactory.apply();
}
).fail(function() {
view.showLoadError('self-assessment');
......@@ -134,9 +141,8 @@ OpenAssessment.SelfView.prototype = {
).done(
function() {
baseView.unsavedWarningEnabled(false, view.UNSAVED_WARNING_KEY);
view.announceStatus = true;
baseView.loadAssessmentModules(usageID);
view.load(usageID);
baseView.scrollToTop(".step--self-assessment");
}
).fail(function(errMsg) {
baseView.toggleActionError('self', errMsg);
......
......@@ -9,6 +9,9 @@ OpenAssessment.StaffView = function(element, server, baseView) {
this.element = element;
this.server = server;
this.baseView = baseView;
this.isRendering = false;
this.announceStatus = false;
};
OpenAssessment.StaffView.prototype = {
......@@ -18,14 +21,16 @@ OpenAssessment.StaffView.prototype = {
**/
load: function(usageID) {
var view = this;
var stepID = ".step--staff-assessment";
var focusID = "[id='oa_staff_grade_" + usageID + "']";
view.isRendering = true;
this.server.render('staff_assessment').done(
function(html) {
$('.step--staff-assessment', view.element).replaceWith(html);
view.isRendering = false;
view.installHandlers();
if (typeof usageID !== 'undefined' &&
$(".step--staff-assessment", view.element).hasClass("is--showing")) {
$("[id='oa_staff_grade_" + usageID + "']", view.element).focus();
}
view.baseView.announceStatusChangeToSRandFocus(stepID, usageID, false, view, focusID);
}
).fail(function() {
view.baseView.showLoadError('staff-assessment');
......
......@@ -14,6 +14,9 @@ OpenAssessment.StudentTrainingView = function(element, server, baseView) {
this.server = server;
this.baseView = baseView;
this.rubric = null;
this.isRendering = false;
this.announceStatus = false;
this.dateFactory = new OpenAssessment.DateTimeFactory(this.element);
};
OpenAssessment.StudentTrainingView.prototype = {
......@@ -24,15 +27,19 @@ OpenAssessment.StudentTrainingView.prototype = {
load: function(usageID) {
var view = this;
var stepID = '.step--student-training';
var focusID = "[id='oa_training_" + usageID + "']";
view.isRendering = true;
this.server.render('student_training').done(
function(html) {
// Load the HTML and install event handlers
$(stepID, view.element).replaceWith(html);
view.isRendering = false;
view.server.renderLatex($(stepID, view.element));
view.installHandlers();
if (typeof usageID !== 'undefined' && $(stepID, view.element).hasClass("is--showing")) {
$("[id='oa_training_" + usageID + "']", view.element).focus();
}
view.baseView.announceStatusChangeToSRandFocus(stepID, usageID, false, view, focusID);
view.announceStatus = false;
view.dateFactory.apply();
}
).fail(function() {
view.baseView.showLoadError('student-training');
......@@ -69,6 +76,7 @@ OpenAssessment.StudentTrainingView.prototype = {
// Handle the click
view.assess();
view.announceStatus = true;
}
);
},
......@@ -91,6 +99,7 @@ OpenAssessment.StudentTrainingView.prototype = {
function(corrections) {
var incorrect = $(".openassessment__student-training--incorrect", view.element);
var instructions = $(".openassessment__student-training--instructions", view.element);
var $questionAnswers = $(".question__answers", view.rubric.element);
if (!view.rubric.showCorrections(corrections)) {
view.load(usageID);
......@@ -100,6 +109,11 @@ OpenAssessment.StudentTrainingView.prototype = {
} else {
instructions.addClass("is--hidden");
incorrect.removeClass("is--hidden");
$questionAnswers.each(function(index, answer) {
var $notification = $(".step__message.message", view.rubric.element).not(".is--hidden");
$(answer).attr('aria-describedby', $($notification[index]).attr('id'));
});
baseView.srReadTexts([gettext('Feedback available for selection.')]);
}
baseView.scrollToTop(".step--student-training");
}
......
......@@ -8,7 +8,8 @@ from openassessment.assessment.api import student_training
from openassessment.workflow import api as workflow_api
from openassessment.workflow.errors import AssessmentWorkflowError
from openassessment.xblock.data_conversion import convert_training_examples_list_to_dict, create_submission_dict
from .resolve_dates import DISTANT_FUTURE, get_current_time_zone
from .resolve_dates import DISTANT_FUTURE
from .user_data import get_user_preferences
logger = logging.getLogger(__name__)
......@@ -69,14 +70,15 @@ class StudentTrainingMixin(object):
# If no submissions have been created yet, the status will be None.
workflow_status = self.get_workflow_info().get('status')
problem_closed, reason, start_date, due_date = self.is_closed(step="student-training")
user_service = self.runtime.service(self, 'user')
user_preferences = get_user_preferences(self.runtime.service(self, 'user'))
context = {"xblock_id": self.get_xblock_id()}
template = 'openassessmentblock/student_training/student_training_unavailable.html'
# add allow_latex field to the context
context['allow_latex'] = self.allow_latex
context['time_zone'] = get_current_time_zone(user_service)
context['user_timezone'] = user_preferences['user_timezone']
context['user_language'] = user_preferences['user_language']
if not workflow_status:
return template, context
......
......@@ -8,7 +8,8 @@ from openassessment.fileupload import api as file_upload_api
from openassessment.fileupload.exceptions import FileUploadError
from openassessment.workflow.errors import AssessmentWorkflowError
from .resolve_dates import DISTANT_FUTURE, get_current_time_zone
from .resolve_dates import DISTANT_FUTURE
from .user_data import get_user_preferences
from data_conversion import create_submission_dict, prepare_submission_for_serialization
from validation import validate_submission
......@@ -376,11 +377,12 @@ class SubmissionMixin(object):
"""
workflow = self.get_workflow_info()
problem_closed, reason, start_date, due_date = self.is_closed('submission')
user_service = self.runtime.service(self, 'user')
user_preferences = get_user_preferences(self.runtime.service(self, 'user'))
path = 'openassessmentblock/response/oa_response.html'
context = {
'time_zone': get_current_time_zone(user_service),
'user_timezone': user_preferences['user_timezone'],
'user_language': user_preferences['user_language'],
"xblock_id": self.get_xblock_id()}
# Due dates can default to the distant future, in which case
......
......@@ -19,7 +19,7 @@ from .base import XBlockHandlerTestCase, scenario
class TestOpenAssessment(XBlockHandlerTestCase):
"""Test Open Asessessment Xblock functionality"""
TIME_ZONE_FN_PATH = 'openassessment.xblock.peer_assessment_mixin.get_current_time_zone'
TIME_ZONE_FN_PATH = 'openassessment.xblock.user_data.get_user_preferences'
@scenario('data/basic_scenario.xml')
def test_load_student_view(self, xblock):
......@@ -100,8 +100,8 @@ class TestOpenAssessment(XBlockHandlerTestCase):
# Expect that the page renders even if the update fails
self.assertIn("OpenAssessmentBlock", xblock_fragment.body_html())
@ddt.data(('utc', 'April 1, 2014 00:00 UTC'),
('America/Los_Angeles', 'March 31, 2014 17:00 PDT'))
@ddt.data(('utc', '2014-03-31 20:00'),
('America/Los_Angeles', '2014-03-31 20:00'))
@ddt.unpack
def test_load_student_view_with_dates(self, time_zone, expected_date):
"""OA XBlock returns some HTML to the user.
......@@ -110,8 +110,8 @@ class TestOpenAssessment(XBlockHandlerTestCase):
Open Assessment XBlock. We don't want to match too heavily against the
contents.
"""
with patch('openassessment.xblock.submission_mixin.get_current_time_zone') as time_zone_fn:
time_zone_fn.return_value = pytz.timezone(time_zone)
with patch('openassessment.xblock.user_data.get_user_preferences') as time_zone_fn:
time_zone_fn.return_value['user_timezone'] = pytz.timezone(time_zone)
xblock = self.load_scenario('data/dates_scenario.xml')
xblock_fragment = self.runtime.render(xblock, "student_view")
......@@ -159,67 +159,40 @@ class TestOpenAssessment(XBlockHandlerTestCase):
request.params = {}
return xblock.render_peer_assessment(request)
@ddt.data(('utc', 'April 1, 2014 01:01 UTC'),
('America/Los_Angeles', 'March 31, 2014 18:01 PDT'))
@ddt.data(('utc', '2014-03-31 21:01'),
('America/Los_Angeles', '2014-03-31 21:01'))
@ddt.unpack
@freeze_time("2014-01-01")
def test_formatted_start_dates(self, time_zone, expected_start_date):
"""Test start dates correctly formatted"""
with patch(self.TIME_ZONE_FN_PATH) as time_zone_fn:
time_zone_fn.return_value = pytz.timezone(time_zone)
time_zone_fn.return_value['user_timezone'] = pytz.timezone(time_zone)
xblock = self._set_up_start_date(dt.datetime(2014, 4, 1, 1, 1, 1))
resp = self._render_xblock(xblock)
self.assertIn(expected_start_date, resp.body)
@ddt.data((dt.datetime(2015, 3, 8, 9, 59, 00, tzinfo=pytz.utc), 'March 8, 2015 01:59 PST'),
(dt.datetime(2015, 3, 8, 10, 00, 00, tzinfo=pytz.utc), 'March 8, 2015 03:00 PDT'))
@ddt.unpack
@freeze_time("2014-01-01")
def test_formatted_start_dates_daylight_savings(self, date, expected_start_date):
"""Test start dates correctly formatted for daylight savings time"""
with patch(self.TIME_ZONE_FN_PATH) as time_zone_fn:
time_zone_fn.return_value = pytz.timezone('America/Los_Angeles')
# Set start dates'
xblock = self._set_up_start_date(date)
resp = self._render_xblock(xblock)
self.assertIn(expected_start_date, resp.body)
@ddt.data(('utc', 'May 1, 2014 00:00 UTC'),
('America/Los_Angeles', 'April 30, 2014 17:00 PDT'))
@ddt.data(('utc', '2014-04-30 20:00'),
('America/Los_Angeles', '2014-04-30 20:00'))
@ddt.unpack
def test_formatted_end_dates(self, time_zone, expected_end_date):
"""Test end dates correctly formatted"""
with patch(self.TIME_ZONE_FN_PATH) as time_zone_fn:
time_zone_fn.return_value = pytz.timezone(time_zone)
time_zone_fn.return_value['user_timezone'] = time_zone
# Set due dates'
xblock = self._set_up_end_date(dt.datetime(2014, 5, 1))
resp = self._render_xblock(xblock)
self.assertIn(expected_end_date, resp.body)
@ddt.data((dt.datetime(2015, 3, 8, 9, 59, 00, tzinfo=pytz.utc), 'March 8, 2015 01:59 PST'),
(dt.datetime(2015, 3, 8, 10, 00, 00, tzinfo=pytz.utc), 'March 8, 2015 03:00 PDT'))
@ddt.unpack
def test_formatted_end_dates_daylight_savings(self, date, expected_end_date):
"""Test end dates correctly formatted for daylight savings time"""
with patch(self.TIME_ZONE_FN_PATH) as time_zone_fn:
time_zone_fn.return_value = pytz.timezone('America/Los_Angeles')
# Set due dates'
xblock = self._set_up_end_date(date)
resp = self._render_xblock(xblock)
self.assertIn(expected_end_date, resp.body)
@ddt.data(('utc', 'April 1, 2014 01:01 UTC'),
('America/Los_Angeles', 'March 31, 2014 18:01 PDT'))
@ddt.data(('utc', '2014-03-31 21:01'),
('America/Los_Angeles', '2014-03-31 21:01'))
@ddt.unpack
@freeze_time("2014-01-01")
def test_formatted_start_dates_for_beta_tester_with_days_early(self, time_zone, expected_start_date):
"""Test start dates for beta tester with days early"""
with patch(self.TIME_ZONE_FN_PATH) as time_zone_fn:
time_zone_fn.return_value = pytz.timezone(time_zone)
time_zone_fn.return_value['user_timezone'] = pytz.timezone(time_zone)
# Set start dates
xblock = self._set_up_start_date(dt.datetime(2014, 4, 6, 1, 1, 1))
......@@ -229,25 +202,24 @@ class TestOpenAssessment(XBlockHandlerTestCase):
resp = self._render_xblock(xblock)
self.assertIn(expected_start_date, resp.body)
@ddt.data(('utc', 'May 1, 2014 00:00 UTC'),
('America/Los_Angeles', 'April 30, 2014 17:00 PDT'))
@ddt.data(('utc', '2014-04-30 20:00'),
('America/Los_Angeles', '2014-04-30 20:00'))
@ddt.unpack
def test_formatted_end_dates_for_beta_tester_with_days_early(self, time_zone, expected_end_date):
"""Test end dates for beta tester with days early"""
with patch(self.TIME_ZONE_FN_PATH) as time_zone_fn:
time_zone_fn.return_value = pytz.timezone(time_zone)
time_zone_fn.return_value['user_timezone'] = pytz.timezone(time_zone)
# Set due dates
xblock = self._set_up_start_date(dt.datetime(2014, 4, 6, 1, 1, 1))
xblock.due = dt.datetime(2014, 5, 1)
self._set_up_days_early_for_beta(xblock, 5)
self.assertEqual(xblock.xmodule_runtime.days_early_for_beta, 5)
resp = self._render_xblock(xblock)
self.assertIn(expected_end_date, resp.body)
@ddt.data(('utc', 'April 6, 2014 01:01 UTC'),
('America/Los_Angeles', 'April 5, 2014 18:01 PDT'))
@ddt.data(('utc', '2014-04-05 21:01'),
('America/Los_Angeles', '2014-04-05 21:01'))
@ddt.unpack
@freeze_time("2014-01-01")
@patch.object(openassessmentblock.OpenAssessmentBlock, 'is_beta_tester', new_callable=PropertyMock)
......@@ -259,7 +231,7 @@ class TestOpenAssessment(XBlockHandlerTestCase):
):
"""Test start dates for beta tester without days early"""
with patch(self.TIME_ZONE_FN_PATH) as time_zone_fn:
time_zone_fn.return_value = pytz.timezone(time_zone)
time_zone_fn.return_value['user_timezone'] = pytz.timezone(time_zone)
mock_is_beta_tester.return_value = True
# Set start dates
......@@ -267,8 +239,8 @@ class TestOpenAssessment(XBlockHandlerTestCase):
resp = self._render_xblock(xblock)
self.assertIn(expected_start_date, resp.body)
@ddt.data(('utc', 'May 1, 2014 00:00 UTC'),
('America/Los_Angeles', 'April 30, 2014 17:00 PDT'))
@ddt.data(('utc', '2014-04-30 20:00'),
('America/Los_Angeles', '2014-04-30 20:00'))
@ddt.unpack
@patch.object(openassessmentblock.OpenAssessmentBlock, 'is_beta_tester', new_callable=PropertyMock)
def test_formatted_end_dates_for_beta_tester_without_days_early(
......@@ -279,7 +251,7 @@ class TestOpenAssessment(XBlockHandlerTestCase):
):
"""Test end dates for beta tester without days early"""
with patch(self.TIME_ZONE_FN_PATH) as time_zone_fn:
time_zone_fn.return_value = pytz.timezone(time_zone)
time_zone_fn.return_value['user_timezone'] = pytz.timezone(time_zone)
mock_is_beta_tester.return_value = True
# Set due dates
......@@ -287,8 +259,8 @@ class TestOpenAssessment(XBlockHandlerTestCase):
resp = self._render_xblock(xblock)
self.assertIn(expected_end_date, resp.body)
@ddt.data(('utc', 'April 6, 2014 01:01 UTC'),
('America/Los_Angeles', 'April 5, 2014 18:01 PDT'))
@ddt.data(('utc', '2014-04-05 21:01'),
('America/Los_Angeles', '2014-04-05 21:01'))
@ddt.unpack
@freeze_time("2014-01-01")
def test_formatted_start_dates_for_beta_tester_with_nonetype_days_early(self, time_zone, expected_start_date):
......@@ -304,8 +276,8 @@ class TestOpenAssessment(XBlockHandlerTestCase):
resp = self._render_xblock(xblock)
self.assertIn(expected_start_date, resp.body)
@ddt.data(('utc', 'May 1, 2014 00:00 UTC'),
('America/Los_Angeles', 'April 30, 2014 17:00 PDT'))
@ddt.data(('utc', '2014-04-30 20:00'),
('America/Los_Angeles', '2014-04-30 20:00'))
@ddt.unpack
def test_formatted_end_dates_for_beta_tester_with_nonetype_days_early(self, time_zone, expected_end_date):
"""Test end dates for beta tester with NoneType days early"""
......
......@@ -309,7 +309,8 @@ class TestPeerAssessmentRender(XBlockHandlerTestCase):
'must_grade': 5,
'review_num': 1,
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
}
self._assert_path_and_context(
xblock, 'openassessmentblock/peer/oa_peer_unavailable.html', expected_context
......@@ -325,7 +326,8 @@ class TestPeerAssessmentRender(XBlockHandlerTestCase):
'must_grade': 5,
'review_num': 1,
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
}
self._assert_path_and_context(
xblock, 'openassessmentblock/peer/oa_peer_closed.html', expected_context
......@@ -341,7 +343,8 @@ class TestPeerAssessmentRender(XBlockHandlerTestCase):
'must_grade': 5,
'review_num': 1,
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
}
self._assert_path_and_context(
xblock, 'openassessmentblock/peer/oa_peer_unavailable.html', expected_context
......@@ -360,7 +363,8 @@ class TestPeerAssessmentRender(XBlockHandlerTestCase):
'review_num': 1,
'submit_button_text': 'submit your assessment & move to response #2',
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
}
self._assert_path_and_context(
xblock, 'openassessmentblock/peer/oa_peer_waiting.html',
......@@ -400,7 +404,8 @@ class TestPeerAssessmentRender(XBlockHandlerTestCase):
'peer_file_url': '',
'submit_button_text': 'submit your assessment & move to response #2',
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
}
self._assert_path_and_context(
xblock, 'openassessmentblock/peer/oa_peer_assessment.html',
......@@ -420,7 +425,8 @@ class TestPeerAssessmentRender(XBlockHandlerTestCase):
'review_num': 1,
'submit_button_text': 'submit your assessment & move to response #2',
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
}
self._assert_path_and_context(
......@@ -444,7 +450,8 @@ class TestPeerAssessmentRender(XBlockHandlerTestCase):
'review_num': 1,
'submit_button_text': 'submit your assessment & move to response #2',
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
}
self._assert_path_and_context(
xblock, 'openassessmentblock/peer/oa_peer_closed.html',
......@@ -480,7 +487,8 @@ class TestPeerAssessmentRender(XBlockHandlerTestCase):
'review_num': 1,
'submit_button_text': 'submit your assessment & move to response #2',
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
}
self._assert_path_and_context(
xblock, 'openassessmentblock/peer/oa_peer_closed.html',
......@@ -508,7 +516,8 @@ class TestPeerAssessmentRender(XBlockHandlerTestCase):
'must_grade': 5,
'review_num': 1,
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
}
self._assert_path_and_context(
......@@ -541,7 +550,8 @@ class TestPeerAssessmentRender(XBlockHandlerTestCase):
'rubric_criteria': xblock.rubric_criteria,
'submit_button_text': 'Submit your assessment & review another response',
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
}
self._assert_path_and_context(
xblock, 'openassessmentblock/peer/oa_peer_turbo_mode_waiting.html',
......@@ -572,7 +582,8 @@ class TestPeerAssessmentRender(XBlockHandlerTestCase):
'rubric_criteria': xblock.rubric_criteria,
'submit_button_text': 'Submit your assessment & review another response',
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
}
self._assert_path_and_context(
xblock, 'openassessmentblock/peer/oa_peer_turbo_mode.html',
......@@ -594,7 +605,8 @@ class TestPeerAssessmentRender(XBlockHandlerTestCase):
'rubric_criteria': xblock.rubric_criteria,
'submit_button_text': 'Submit your assessment & review another response',
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
}
self._assert_path_and_context(
xblock, 'openassessmentblock/peer/oa_peer_unavailable.html',
......
......@@ -6,7 +6,9 @@ import datetime
from django.test import TestCase
import ddt
from mock import MagicMock
from openassessment.xblock.resolve_dates import resolve_dates, DISTANT_PAST, DISTANT_FUTURE, get_current_time_zone
from openassessment.xblock.resolve_dates import resolve_dates, DISTANT_PAST, DISTANT_FUTURE
from openassessment.xblock.user_data import get_user_preferences
import pytz
from workbench.runtime import WorkBenchUserService
......@@ -131,13 +133,14 @@ class ResolveDatesTest(TestCase):
STUB_I18N
)
@ddt.data(({}, pytz.utc),
({'pref-lang': 'en', 'time_zone': 'America/Los_Angeles'}, pytz.timezone('America/Los_Angeles')))
@ddt.data(({}, None, None),
({'pref-lang': 'en', 'time_zone': 'America/Los_Angeles'}, 'America/Los_Angeles', 'en'))
@ddt.unpack
def test_get_current_time_zone(self, user_preferences, expected_time_zone):
"""Verify get_current_time_zone returns correct time zone or UTC"""
def test_get_user_preferences(self, user_preferences, expected_timezone, expected_language):
"""Verify get_user_preferences returns correct time zone and language"""
user_service = WorkBenchUserService(3)
user_service.get_current_user().opt_attrs['edx-platform.user_preferences'] = user_preferences
time_zone = get_current_time_zone(user_service)
self.assertEqual(expected_time_zone, time_zone)
user_preferences = get_user_preferences(user_service)
self.assertEqual(expected_timezone, user_preferences['user_timezone'])
self.assertEqual(expected_language, user_preferences['user_language'])
......@@ -172,7 +172,8 @@ class TestSelfAssessmentRender(XBlockHandlerTestCase):
{
'self_start': datetime.datetime(5999, 1, 1).replace(tzinfo=pytz.utc),
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
}
)
......@@ -185,7 +186,8 @@ class TestSelfAssessmentRender(XBlockHandlerTestCase):
{
'self_due': datetime.datetime(2000, 1, 1).replace(tzinfo=pytz.utc),
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
}
)
......@@ -196,7 +198,8 @@ class TestSelfAssessmentRender(XBlockHandlerTestCase):
xblock, 'openassessmentblock/self/oa_self_unavailable.html',
{
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
}
)
......@@ -212,7 +215,8 @@ class TestSelfAssessmentRender(XBlockHandlerTestCase):
xblock, 'openassessmentblock/self/oa_self_unavailable.html',
{
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
}
)
......@@ -228,7 +232,8 @@ class TestSelfAssessmentRender(XBlockHandlerTestCase):
xblock, 'openassessmentblock/self/oa_self_complete.html',
{
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': None,
'user_language': None
},
workflow_status='waiting',
status_details={
......@@ -249,7 +254,9 @@ class TestSelfAssessmentRender(XBlockHandlerTestCase):
xblock, 'openassessmentblock/self/oa_self_complete.html',
{
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': None,
'user_language': None
},
workflow_status="peer",
status_details={
......@@ -268,7 +275,8 @@ class TestSelfAssessmentRender(XBlockHandlerTestCase):
xblock, 'openassessmentblock/self/oa_self_complete.html',
{
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': None,
'user_language': None
},
workflow_status='done'
)
......@@ -283,7 +291,8 @@ class TestSelfAssessmentRender(XBlockHandlerTestCase):
xblock, 'openassessmentblock/self/oa_self_cancelled.html',
{
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
},
workflow_status='cancelled'
)
......@@ -302,7 +311,8 @@ class TestSelfAssessmentRender(XBlockHandlerTestCase):
'file_upload_type': None,
'self_file_url': '',
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
},
workflow_status='self',
submission_uuid=submission['uuid']
......@@ -326,7 +336,8 @@ class TestSelfAssessmentRender(XBlockHandlerTestCase):
xblock, 'openassessmentblock/self/oa_self_complete.html',
{
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
},
workflow_status='self',
submission_uuid=submission['uuid']
......@@ -345,7 +356,8 @@ class TestSelfAssessmentRender(XBlockHandlerTestCase):
{
'self_due': datetime.datetime(2000, 1, 1).replace(tzinfo=pytz.utc),
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
},
workflow_status='self',
submission_uuid=submission['uuid']
......@@ -376,7 +388,8 @@ class TestSelfAssessmentRender(XBlockHandlerTestCase):
{
'self_due': datetime.datetime(2000, 1, 1).replace(tzinfo=pytz.utc),
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': pytz.utc,
'user_language': 'en'
},
workflow_status='self',
submission_uuid=submission['uuid']
......
......@@ -66,7 +66,8 @@ class StudentTrainingAssessTest(StudentTrainingTest):
@ddt.file_data('data/student_training_mixin.json')
def test_correct(self, xblock, data):
xblock.create_submission(xblock.get_student_item_dict(), self.SUBMISSION)
data["expected_context"]['time_zone'] = pytz.utc
data["expected_context"]['user_timezone'] = None
data["expected_context"]['user_language'] = None
self.assert_path_and_context(xblock, data["expected_template"], data["expected_context"])
# Agree with the course author's assessment
......@@ -87,7 +88,8 @@ class StudentTrainingAssessTest(StudentTrainingTest):
@ddt.file_data('data/student_training_mixin.json')
def test_correct_with_error(self, xblock, data):
xblock.create_submission(xblock.get_student_item_dict(), self.SUBMISSION)
data["expected_context"]['time_zone'] = pytz.utc
data["expected_context"]['user_timezone'] = None
data["expected_context"]['user_language'] = None
self.assert_path_and_context(xblock, data["expected_template"], data["expected_context"])
# Agree with the course author's assessment
......@@ -111,7 +113,8 @@ class StudentTrainingAssessTest(StudentTrainingTest):
@ddt.file_data('data/student_training_mixin.json')
def test_incorrect(self, xblock, data):
xblock.create_submission(xblock.get_student_item_dict(), self.SUBMISSION)
data["expected_context"]['time_zone'] = pytz.utc
data["expected_context"]['user_timezone'] = None
data["expected_context"]['user_language'] = None
self.assert_path_and_context(xblock, data["expected_template"], data["expected_context"])
# Disagree with the course author's assessment
......@@ -134,7 +137,8 @@ class StudentTrainingAssessTest(StudentTrainingTest):
expected_context = data["expected_context"].copy()
expected_template = data["expected_template"]
xblock.create_submission(xblock.get_student_item_dict(), self.SUBMISSION)
expected_context['time_zone'] = pytz.utc
expected_context['user_timezone'] = None
expected_context['user_language'] = None
self.assert_path_and_context(xblock, expected_template, expected_context)
# Agree with the course author's assessment
......@@ -182,7 +186,8 @@ class StudentTrainingAssessTest(StudentTrainingTest):
self.assertFalse(resp['corrections'])
expected_context = {
"allow_latex": False,
'time_zone': pytz.utc,
'user_timezone': None,
'user_language': None
}
expected_template = "openassessmentblock/student_training/student_training_complete.html"
self.assert_path_and_context(xblock, expected_template, expected_context)
......@@ -213,7 +218,9 @@ class StudentTrainingAssessTest(StudentTrainingTest):
xblock.create_submission(xblock.get_student_item_dict(), self.SUBMISSION)
expected_context = data["expected_context"].copy()
expected_template = data["expected_template"]
expected_context['time_zone'] = pytz.utc
expected_context['user_timezone'] = None
expected_context['user_language'] = None
self.assert_path_and_context(xblock, expected_template, expected_context)
resp = self.request(xblock, 'training_assess', json.dumps({}), response_format='json')
self.assertFalse(resp['success'], msg=resp.get('msg'))
......@@ -230,7 +237,8 @@ class StudentTrainingAssessTest(StudentTrainingTest):
xblock.create_submission(xblock.get_student_item_dict(), self.SUBMISSION)
expected_context = data["expected_context"].copy()
expected_template = data["expected_template"]
expected_context['time_zone'] = pytz.utc
expected_context['user_timezone'] = None
expected_context['user_language'] = None
self.assert_path_and_context(xblock, expected_template, expected_context)
selected_data = {
......@@ -317,7 +325,8 @@ class StudentTrainingRenderTest(StudentTrainingTest):
expected_context = {
'training_due': "2000-01-01T00:00:00+00:00",
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': None,
'user_language': None
}
self.assert_path_and_context(xblock, expected_template, expected_context)
......@@ -331,7 +340,8 @@ class StudentTrainingRenderTest(StudentTrainingTest):
expected_template = "openassessmentblock/student_training/student_training_cancelled.html"
expected_context = {
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': None,
'user_language': None
}
self.assert_path_and_context(xblock, expected_template, expected_context)
......@@ -350,6 +360,7 @@ class StudentTrainingRenderTest(StudentTrainingTest):
expected_context = {
'training_start': datetime.datetime(3000, 1, 1).replace(tzinfo=pytz.utc),
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': None,
'user_language': None
}
self.assert_path_and_context(xblock, expected_template, expected_context)
......@@ -182,7 +182,8 @@ class SubmissionRenderTest(XBlockHandlerTestCase):
'file_upload_type': None,
'submission_start': dt.datetime(4999, 4, 1).replace(tzinfo=pytz.utc),
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': None,
'user_language': None
}
)
......@@ -205,7 +206,8 @@ class SubmissionRenderTest(XBlockHandlerTestCase):
'peer_incomplete': True,
'self_incomplete': True,
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': None,
'user_language': None
}
)
......@@ -224,7 +226,8 @@ class SubmissionRenderTest(XBlockHandlerTestCase):
'submit_enabled': False,
'submission_due': dt.datetime(2999, 5, 6).replace(tzinfo=pytz.utc),
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': None,
'user_language': None
}
)
......@@ -242,7 +245,8 @@ class SubmissionRenderTest(XBlockHandlerTestCase):
'save_status': 'This response has not been saved.',
'submit_enabled': False,
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': None,
'user_language': None
}
)
......@@ -266,7 +270,8 @@ class SubmissionRenderTest(XBlockHandlerTestCase):
'submit_enabled': True,
'submission_due': dt.datetime(2999, 5, 6).replace(tzinfo=pytz.utc),
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': None,
'user_language': None
}
)
......@@ -290,7 +295,8 @@ class SubmissionRenderTest(XBlockHandlerTestCase):
'submit_enabled': True,
'submission_due': dt.datetime(2999, 5, 6).replace(tzinfo=pytz.utc),
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': None,
'user_language': None
}
)
......@@ -309,7 +315,8 @@ class SubmissionRenderTest(XBlockHandlerTestCase):
'peer_incomplete': True,
'self_incomplete': True,
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': None,
'user_language': None
}
)
......@@ -341,7 +348,8 @@ class SubmissionRenderTest(XBlockHandlerTestCase):
'cancelled_by_id': 'Bob',
'cancelled_by': mock_staff
},
'time_zone': pytz.utc,
'user_timezone': None,
'user_language': None
}
)
......@@ -367,7 +375,8 @@ class SubmissionRenderTest(XBlockHandlerTestCase):
'peer_incomplete': True,
'self_incomplete': True,
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': None,
'user_language': None
}
)
......@@ -379,7 +388,8 @@ class SubmissionRenderTest(XBlockHandlerTestCase):
'file_upload_type': None,
'submission_due': dt.datetime(2014, 4, 5).replace(tzinfo=pytz.utc),
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': None,
'user_language': None
}
)
......@@ -398,7 +408,8 @@ class SubmissionRenderTest(XBlockHandlerTestCase):
'peer_incomplete': False,
'self_incomplete': True,
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': None,
'user_language': None
}
)
......@@ -423,7 +434,8 @@ class SubmissionRenderTest(XBlockHandlerTestCase):
'student_submission': create_submission_dict(submission, xblock.prompts),
'file_upload_type': None,
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': None,
'user_language': None
}
)
......@@ -448,7 +460,8 @@ class SubmissionRenderTest(XBlockHandlerTestCase):
'student_submission': create_submission_dict(submission, xblock.prompts),
'file_upload_type': None,
'allow_latex': False,
'time_zone': pytz.utc,
'user_timezone': None,
'user_language': None
}
)
......@@ -457,7 +470,7 @@ class SubmissionRenderTest(XBlockHandlerTestCase):
# Expect that the response step is open and displays the deadline
resp = self.request(xblock, 'render_submission', json.dumps(dict()))
self.assertIn('Enter your response to the question', resp)
self.assertIn('Monday, May 6, 2999 00:00 UTC', resp)
self.assertIn('2999-05-05 19:00', resp)
# Create a submission for the user
xblock.create_submission(
......
"""
Retrieve user-specific data
"""
def get_user_preferences(user_service):
"""
Returns the preferred language and timezone for the current user, if specified, or None if not.
:param user_service: XblockUserService
"""
user_preferences = {
'user_language': None,
'user_timezone': None
}
retrieved_preferences = user_service.get_current_user().opt_attrs.get('edx-platform.user_preferences')
if retrieved_preferences is not None:
user_preferences['user_timezone'] = retrieved_preferences.get('time_zone')
user_preferences['user_language'] = retrieved_preferences.get('pref-lang')
return user_preferences
......@@ -2,23 +2,29 @@
"name": "edx-ora2",
"version": "0.2.0",
"repository": "https://github.com/edx/edx-ora2.git",
"dependencies": {
"edx-ui-toolkit": "1.5.1",
"moment": "^2.15.1",
"moment-timezone": "~0.5.5",
"requirejs": "~2.3.2",
"underscore": "^1.8.2"
},
"devDependencies": {
"underscore": "^1.8.2",
"jasmine": "^2.3.0",
"jscs": "2.6.0",
"jshint": "2.8.0",
"karma": "^0.12.16",
"karma-chrome-launcher": "^0.1.4",
"karma-coverage": "^0.2.6",
"karma-jasmine": "^0.3.6",
"karma-jasmine-html-reporter": "~0.1",
"karma-jasmine-jquery": "^0.1.1",
"karma-chrome-launcher": "^0.1.4",
"karma-phantomjs-launcher": "^0.1.4",
"karma-sinon": "^1.0.3",
"karma-jasmine-html-reporter": "~0.1",
"karma-spec-reporter": "^0.0.20",
"jasmine": "^2.3.0",
"phantomjs": "^1.9.11",
"sinon": "^1.10.3",
"uglify-js": "^2.6.3",
"jshint": "2.8.0",
"jscs": "2.6.0"
"uglify-js": "^2.6.3"
},
"scripts": {
"test": "./node_modules/karma/bin/karma start --reporters spec,coverage"
......
(function(require, define) {
'use strict';
var defineDependency;
// We do not wish to bundle common libraries (that may also be used by non-RequireJS code on the page
// into the optimized files. Therefore load these libraries through script tags and explicitly define them.
// Note that when the optimizer executes this code, window will not be defined.
if (window) {
defineDependency = function (globalName, name, noShim) {
var getGlobalValue = function () {
var globalNamePath = globalName.split('.'),
result = window,
i;
for (i = 0; i < globalNamePath.length; i++) {
result = result[globalNamePath[i]];
}
return result;
},
globalValue = getGlobalValue();
if (globalValue) {
if (noShim) {
define(name, {});
} else {
define(name, [], function () {
return globalValue;
});
}
} else {
console.error('Expected library to be included on page, but not found on window object: ' + name);
}
};
defineDependency('jQuery', 'jquery');
}
function getBaseUrlPath() {
// require-config doesn't play nice with relative paths, but
// the Travis server, devstack, and staging/production servers all place the
// node_module files differently in absolute paths - this is a way to retrieve the
// path to the Jasmine req's as nicely as possible
var scripts = document.getElementsByTagName("script");
var fullTag = $(scripts[scripts.length-1]);
var baseUrl = fullTag.attr('src').split(['require-config'])[0];
return baseUrl
}
require.config({
baseUrl: getBaseUrlPath(),
paths: {
'jquery': './openassessment/xblock/static/js/lib/jquery.min',
'moment': './node_modules/moment/min/moment-with-locales.min',
'moment-timezone': './node_modules/moment-timezone/builds/moment-timezone-with-data.min',
'edx-ui-toolkit/js/utils/date-utils': './node_modules/edx-ui-toolkit/src/js/utils/date-utils',
'edx-ui-toolkit/js/utils/string-utils': './node_modules/edx-ui-toolkit/src/js/utils/string-utils'
},
shim: {
'jquery': {
exports: 'jQuery'
}
}
})
}).call(this, require || RequireJS.require, define || RequireJS.define);
......@@ -12,7 +12,7 @@ git+https://github.com/edx/edx-submissions.git@1.1.4#egg=edx-submissions==1.1.4
boto>=2.32.1,<3.0.0
celery==3.1.18
defusedxml==0.4.1
django>=1.8.14,<1.9
django<1.9a0 # Resolves known bug on gemnasium. See TNL-6266
django-extensions==1.5.9
django-model-utils==2.3.1
djangorestframework>=3.1,<3.3
......
......@@ -38,6 +38,15 @@ class BaseAssessmentPage(PageObject):
loc=self._problem_location
)
def get_sr_html(self):
return self.q(css='.sr.reader-feedback').html
def confirm_feedback_text(self, text):
def is_text_in_feedback():
return text in self.get_sr_html()[0]
self.wait_for(is_text_in_feedback, 'Waiting for %s, in %s' % (text, self.q(css='.sr.reader-feedback').html[0]))
class MultipleAssessmentPage(BaseAssessmentPage):
"""
......@@ -76,7 +85,6 @@ class OpenAssessmentPage(BaseAssessmentPage):
"""
return ".vert-{vertical_index}".format(vertical_index=self.vertical_index)
def submit(self, button_css=".action--submit"):
"""
Click the submit button on the page.
......
......@@ -792,14 +792,20 @@ class FullWorkflowMixin(object):
username, email = self.do_submission()
EmptyPromise(self.submission_page.button(".step--student-training").is_focused(),
"Student training button should be focused")
self.submission_page.confirm_feedback_text('Your Response Complete')
self.submission_page.confirm_feedback_text('Learn to Assess Responses In Progress (1 of 2)')
self.do_training()
EmptyPromise(self.submission_page.button(".step--self-assessment").is_focused(),
"Self assessment button should be focused")
self.submission_page.confirm_feedback_text('Learn to Assess Responses Complete')
self.submission_page.confirm_feedback_text('Assess Your Response In Progress')
self.submit_self_assessment(self.SELF_ASSESSMENT)
EmptyPromise(self.submission_page.button(".step--grade").is_focused(),
"Grade button should be focused")
self.submission_page.confirm_feedback_text('Assess Your Response Complete')
self.submission_page.confirm_feedback_text('Assess Peers In Progress (1 of 1)')
return username, email
......
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