upgrade backbone-associations, fix tests
Merge pull request #17559 from edx/ri/EDUCATOR-2231-remove-enrollment-dates-references EDUCATOR-2231 remove course run enrollment dates references from studio add NoTextbooks component make backbone-associations work webpackify context course test fixup
This commit is contained in:
@@ -29,8 +29,8 @@ class CourseAccessRoleSerializer(serializers.ModelSerializer):
|
||||
class CourseRunScheduleSerializer(serializers.Serializer):
|
||||
start = serializers.DateTimeField()
|
||||
end = serializers.DateTimeField()
|
||||
enrollment_start = serializers.DateTimeField(allow_null=True)
|
||||
enrollment_end = serializers.DateTimeField(allow_null=True)
|
||||
enrollment_start = serializers.DateTimeField(allow_null=True, required=False)
|
||||
enrollment_end = serializers.DateTimeField(allow_null=True, required=False)
|
||||
|
||||
|
||||
class CourseRunTeamSerializer(serializers.Serializer):
|
||||
|
||||
@@ -16,39 +16,46 @@ from ...serializers.course_runs import CourseRunSerializer
|
||||
@ddt.ddt
|
||||
class CourseRunSerializerTests(ModuleStoreTestCase):
|
||||
|
||||
@ddt.data(
|
||||
('instructor_paced', False),
|
||||
('self_paced', True),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_data(self, expected_pacing_type, self_paced):
|
||||
start = datetime.datetime.now(pytz.UTC)
|
||||
end = start + datetime.timedelta(days=30)
|
||||
enrollment_start = start - datetime.timedelta(days=7)
|
||||
enrollment_end = end - datetime.timedelta(days=14)
|
||||
def setUp(self):
|
||||
super(CourseRunSerializerTests, self).setUp()
|
||||
|
||||
course = CourseFactory(
|
||||
start=start,
|
||||
end=end,
|
||||
self.course_start = datetime.datetime.now(pytz.UTC)
|
||||
self.course_end = self.course_start + datetime.timedelta(days=30)
|
||||
|
||||
self.request = RequestFactory().get('')
|
||||
|
||||
def setup_course(self, self_paced, enrollment_start=None, enrollment_end=None):
|
||||
return CourseFactory(
|
||||
start=self.course_start,
|
||||
end=self.course_end,
|
||||
self_paced=self_paced,
|
||||
enrollment_start=enrollment_start,
|
||||
enrollment_end=enrollment_end,
|
||||
self_paced=self_paced
|
||||
enrollment_end=enrollment_end
|
||||
)
|
||||
|
||||
def setup_course_user_roles(self, course):
|
||||
"""
|
||||
get course staff and instructor roles user
|
||||
"""
|
||||
instructor = UserFactory()
|
||||
CourseInstructorRole(course.id).add_users(instructor)
|
||||
staff = UserFactory()
|
||||
CourseStaffRole(course.id).add_users(staff)
|
||||
|
||||
request = RequestFactory().get('')
|
||||
serializer = CourseRunSerializer(course, context={'request': request})
|
||||
expected = {
|
||||
return instructor, staff
|
||||
|
||||
def get_expected_course_data(
|
||||
self, course, enrollment_start, enrollment_end,
|
||||
instructor, staff, expected_pacing_type
|
||||
):
|
||||
return {
|
||||
'id': str(course.id),
|
||||
'title': course.display_name,
|
||||
'schedule': {
|
||||
'start': serialize_datetime(start),
|
||||
'end': serialize_datetime(end),
|
||||
'enrollment_start': serialize_datetime(enrollment_start),
|
||||
'enrollment_end': serialize_datetime(enrollment_end),
|
||||
'start': serialize_datetime(self.course_start),
|
||||
'end': serialize_datetime(self.course_end),
|
||||
'enrollment_start': enrollment_start,
|
||||
'enrollment_end': enrollment_end,
|
||||
},
|
||||
'team': [
|
||||
{
|
||||
@@ -61,8 +68,50 @@ class CourseRunSerializerTests(ModuleStoreTestCase):
|
||||
},
|
||||
],
|
||||
'images': {
|
||||
'card_image': request.build_absolute_uri(course_image_url(course)),
|
||||
'card_image': self.request.build_absolute_uri(course_image_url(course)),
|
||||
},
|
||||
'pacing_type': expected_pacing_type,
|
||||
}
|
||||
assert serializer.data == expected
|
||||
|
||||
@ddt.data(
|
||||
('instructor_paced', False),
|
||||
('self_paced', True),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_data_with_enrollment_dates(self, expected_pacing_type, self_paced):
|
||||
"""
|
||||
Verify that CourseRunSerializer serializes the course object.
|
||||
"""
|
||||
|
||||
enrollment_start = self.course_start - datetime.timedelta(days=7)
|
||||
enrollment_end = self.course_end - datetime.timedelta(days=14)
|
||||
course = self.setup_course(self_paced, enrollment_start, enrollment_end)
|
||||
instructor, staff = self.setup_course_user_roles(course)
|
||||
serializer = CourseRunSerializer(course, context={'request': self.request})
|
||||
|
||||
expected_course_data = self.get_expected_course_data(
|
||||
course, serialize_datetime(enrollment_start), serialize_datetime(enrollment_end),
|
||||
instructor, staff, expected_pacing_type
|
||||
)
|
||||
|
||||
assert serializer.data == expected_course_data
|
||||
|
||||
@ddt.data(
|
||||
('instructor_paced', False),
|
||||
('self_paced', True),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_data_without_enrollment_dates(self, expected_pacing_type, self_paced):
|
||||
"""
|
||||
Verify that CourseRunSerializer serializes the course object without enrollment
|
||||
start and end dates.
|
||||
"""
|
||||
course = self.setup_course(self_paced)
|
||||
instructor, staff = self.setup_course_user_roles(course)
|
||||
serializer = CourseRunSerializer(course, context={'request': self.request})
|
||||
|
||||
expected_course_data = self.get_expected_course_data(
|
||||
course, None, None, instructor, staff, expected_pacing_type
|
||||
)
|
||||
|
||||
assert serializer.data == expected_course_data
|
||||
|
||||
@@ -34,7 +34,7 @@ class CourseRunViewSetTests(ModuleStoreTestCase):
|
||||
user = AdminFactory()
|
||||
self.client.login(username=user.username, password=TEST_PASSWORD)
|
||||
|
||||
def get_course_run_data(self, user, start, end, enrollment_start, enrollment_end, pacing_type, role='instructor'):
|
||||
def get_course_run_data(self, user, start, end, pacing_type, role='instructor'):
|
||||
return {
|
||||
'title': 'Testing 101',
|
||||
'org': 'TestingX',
|
||||
@@ -43,8 +43,6 @@ class CourseRunViewSetTests(ModuleStoreTestCase):
|
||||
'schedule': {
|
||||
'start': serialize_datetime(start),
|
||||
'end': serialize_datetime(end),
|
||||
'enrollment_start': serialize_datetime(enrollment_start),
|
||||
'enrollment_end': serialize_datetime(enrollment_end),
|
||||
},
|
||||
'team': [
|
||||
{
|
||||
@@ -55,11 +53,9 @@ class CourseRunViewSetTests(ModuleStoreTestCase):
|
||||
'pacing_type': pacing_type,
|
||||
}
|
||||
|
||||
def assert_course_run_schedule(self, course_run, start, end, enrollment_start, enrollment_end):
|
||||
def assert_course_run_schedule(self, course_run, start, end):
|
||||
assert course_run.start == start
|
||||
assert course_run.end == end
|
||||
assert course_run.enrollment_start == enrollment_start
|
||||
assert course_run.enrollment_end == enrollment_end
|
||||
|
||||
def assert_access_role(self, course_run, user, role):
|
||||
# An error will be raised if the endpoint did not create the role
|
||||
@@ -111,14 +107,12 @@ class CourseRunViewSetTests(ModuleStoreTestCase):
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_update(self):
|
||||
course_run = CourseFactory(start=None, end=None, enrollment_start=None, enrollment_end=None)
|
||||
course_run = CourseFactory(start=None, end=None)
|
||||
assert CourseAccessRole.objects.filter(course_id=course_run.id).count() == 0
|
||||
|
||||
url = reverse('api:v1:course_run-detail', kwargs={'pk': str(course_run.id)})
|
||||
start = datetime.datetime.now(pytz.UTC).replace(microsecond=0)
|
||||
end = start + datetime.timedelta(days=30)
|
||||
enrollment_start = start - datetime.timedelta(days=7)
|
||||
enrollment_end = end - datetime.timedelta(days=14)
|
||||
title = 'A New Testing Strategy'
|
||||
user = UserFactory()
|
||||
role = 'staff'
|
||||
@@ -127,8 +121,6 @@ class CourseRunViewSetTests(ModuleStoreTestCase):
|
||||
'schedule': {
|
||||
'start': serialize_datetime(start),
|
||||
'end': serialize_datetime(end),
|
||||
'enrollment_start': serialize_datetime(enrollment_start),
|
||||
'enrollment_end': serialize_datetime(enrollment_end),
|
||||
},
|
||||
'team': [
|
||||
{
|
||||
@@ -145,7 +137,7 @@ class CourseRunViewSetTests(ModuleStoreTestCase):
|
||||
course_run = modulestore().get_course(course_run.id)
|
||||
assert response.data == CourseRunSerializer(course_run, context=self.get_serializer_context()).data
|
||||
assert course_run.display_name == title
|
||||
self.assert_course_run_schedule(course_run, start, end, enrollment_start, enrollment_end)
|
||||
self.assert_course_run_schedule(course_run, start, end)
|
||||
|
||||
def test_update_with_invalid_user(self):
|
||||
course_run = CourseFactory()
|
||||
@@ -168,7 +160,7 @@ class CourseRunViewSetTests(ModuleStoreTestCase):
|
||||
Test that update run updates the pacing type
|
||||
"""
|
||||
start = datetime.datetime.now(pytz.UTC).replace(microsecond=0)
|
||||
course_run = CourseFactory(start=start, end=None, enrollment_start=None, enrollment_end=None, self_paced=False)
|
||||
course_run = CourseFactory(start=start, end=None, self_paced=False)
|
||||
data = {
|
||||
'pacing_type': 'self_paced',
|
||||
}
|
||||
@@ -178,7 +170,7 @@ class CourseRunViewSetTests(ModuleStoreTestCase):
|
||||
|
||||
course_run = modulestore().get_course(course_run.id)
|
||||
assert course_run.self_paced is True
|
||||
self.assert_course_run_schedule(course_run, start, None, None, None)
|
||||
self.assert_course_run_schedule(course_run, start, None)
|
||||
|
||||
def test_update_with_instructor_role(self):
|
||||
"""
|
||||
@@ -187,7 +179,7 @@ class CourseRunViewSetTests(ModuleStoreTestCase):
|
||||
instructor_role = 'instructor'
|
||||
start = datetime.datetime.now(pytz.UTC).replace(microsecond=0)
|
||||
new_user = UserFactory()
|
||||
course_run = CourseFactory(start=start, end=None, enrollment_start=None, enrollment_end=None, self_paced=False)
|
||||
course_run = CourseFactory(start=start, end=None, self_paced=False)
|
||||
assert CourseAccessRole.objects.filter(course_id=course_run.id).count() == 0
|
||||
data = {
|
||||
'team': [
|
||||
@@ -217,7 +209,7 @@ class CourseRunViewSetTests(ModuleStoreTestCase):
|
||||
staff_role = 'staff'
|
||||
instructor_role = 'instructor'
|
||||
start = datetime.datetime.now(pytz.UTC).replace(microsecond=0)
|
||||
course_run = CourseFactory(start=start, end=None, enrollment_start=None, enrollment_end=None, self_paced=False)
|
||||
course_run = CourseFactory(start=start, end=None, self_paced=False)
|
||||
|
||||
existing_user = UserFactory()
|
||||
CourseAccessRole.objects.create(
|
||||
@@ -259,10 +251,8 @@ class CourseRunViewSetTests(ModuleStoreTestCase):
|
||||
user = UserFactory()
|
||||
start = datetime.datetime.now(pytz.UTC).replace(microsecond=0)
|
||||
end = start + datetime.timedelta(days=30)
|
||||
enrollment_start = start - datetime.timedelta(days=7)
|
||||
enrollment_end = end - datetime.timedelta(days=14)
|
||||
role = 'staff'
|
||||
data = self.get_course_run_data(user, start, end, enrollment_start, enrollment_end, pacing_type, role)
|
||||
data = self.get_course_run_data(user, start, end, pacing_type, role)
|
||||
|
||||
response = self.client.post(self.list_url, data, format='json')
|
||||
self.assertEqual(response.status_code, 201)
|
||||
@@ -274,7 +264,7 @@ class CourseRunViewSetTests(ModuleStoreTestCase):
|
||||
self.assertEqual(course_run.id.course, data['number'])
|
||||
self.assertEqual(course_run.id.run, data['run'])
|
||||
self.assertEqual(course_run.self_paced, expected_self_paced_value)
|
||||
self.assert_course_run_schedule(course_run, start, end, enrollment_start, enrollment_end)
|
||||
self.assert_course_run_schedule(course_run, start, end)
|
||||
self.assert_access_role(course_run, user, role)
|
||||
self.assert_course_access_role_count(course_run, 1)
|
||||
|
||||
@@ -286,9 +276,7 @@ class CourseRunViewSetTests(ModuleStoreTestCase):
|
||||
user = UserFactory()
|
||||
start = datetime.datetime.now(pytz.UTC).replace(microsecond=0)
|
||||
end = start + datetime.timedelta(days=30)
|
||||
enrollment_start = start - datetime.timedelta(days=7)
|
||||
enrollment_end = end - datetime.timedelta(days=14)
|
||||
data = self.get_course_run_data(user, start, end, enrollment_start, enrollment_end, 'self-paced')
|
||||
data = self.get_course_run_data(user, start, end, 'self-paced')
|
||||
data['team'] = [{'user': 'invalid-username'}]
|
||||
response = self.client.post(self.list_url, data, format='json')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
@@ -345,8 +333,6 @@ class CourseRunViewSetTests(ModuleStoreTestCase):
|
||||
'schedule': {
|
||||
'start': serialize_datetime(start),
|
||||
'end': serialize_datetime(end),
|
||||
'enrollment_start': None,
|
||||
'enrollment_end': None,
|
||||
},
|
||||
'team': [
|
||||
{
|
||||
@@ -363,7 +349,7 @@ class CourseRunViewSetTests(ModuleStoreTestCase):
|
||||
course_run = modulestore().get_course(course_run_key)
|
||||
assert course_run.id.run == run
|
||||
assert course_run.self_paced is expected_self_paced_value
|
||||
self.assert_course_run_schedule(course_run, start, end, None, None)
|
||||
self.assert_course_run_schedule(course_run, start, end)
|
||||
self.assert_access_role(course_run, user, role)
|
||||
self.assert_course_access_role_count(course_run, 1)
|
||||
|
||||
|
||||
24
cms/static/js/features_jsx/studio/NoTextbooks.jsx
Normal file
24
cms/static/js/features_jsx/studio/NoTextbooks.jsx
Normal file
@@ -0,0 +1,24 @@
|
||||
/* global gettext */
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
export class NoTextbooks extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="no-textbook-content">
|
||||
<p>
|
||||
{gettext("You haven't added any textbooks to this course yet.")}
|
||||
<a href="#" className="button new-button">
|
||||
<span className="icon fa fa-plus" aria-hidden="true"></span>
|
||||
{gettext("Add your first textbook")}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NoTextbooks.propTypes = {
|
||||
|
||||
};
|
||||
6
cms/static/js/pages/course.js
Normal file
6
cms/static/js/pages/course.js
Normal file
@@ -0,0 +1,6 @@
|
||||
define(
|
||||
['js/models/course'],
|
||||
function(ContextCourse) {
|
||||
window.course = new ContextCourse(window.pageFactoryArguments.ContextCourse[0]);
|
||||
}
|
||||
);
|
||||
7
cms/static/js/pages/textbooks.js
Normal file
7
cms/static/js/pages/textbooks.js
Normal file
@@ -0,0 +1,7 @@
|
||||
define(
|
||||
['js/factories/textbooks', 'common/js/utils/page_factory', 'js/pages/course'],
|
||||
function(TextbooksFactory, invokePageFactory) {
|
||||
'use strict';
|
||||
invokePageFactory('TextbooksFactory', TextbooksFactory);
|
||||
}
|
||||
);
|
||||
@@ -123,19 +123,29 @@ from openedx.core.djangolib.markup import HTML
|
||||
<%block name="jsextra"></%block>
|
||||
<%block name="page_bundle">
|
||||
<script type="text/javascript">
|
||||
if (typeof window.pageFactoryArguments == "undefined") {
|
||||
window.pageFactoryArguments = {};
|
||||
}
|
||||
% if context_course:
|
||||
window.pageFactoryArguments['ContextCourse'] = {
|
||||
id: "${context_course.id | n, js_escaped_string}",
|
||||
name: "${context_course.display_name_with_default | n, js_escaped_string}",
|
||||
url_name: "${context_course.location.block_id | n, js_escaped_string}",
|
||||
org: "${context_course.location.org | n, js_escaped_string}",
|
||||
num: "${context_course.location.course | n, js_escaped_string}",
|
||||
display_course_number: "${context_course.display_coursenumber | n, js_escaped_string}",
|
||||
revision: "${context_course.location.branch | n, js_escaped_string}",
|
||||
self_paced: ${ context_course.self_paced | n, dump_js_escaped_json }
|
||||
}
|
||||
% endif
|
||||
|
||||
|
||||
require(['js/factories/base'], function () {
|
||||
require(['js/models/course'], function(Course) {
|
||||
% if context_course:
|
||||
window.course = new Course({
|
||||
id: "${context_course.id | n, js_escaped_string}",
|
||||
name: "${context_course.display_name_with_default | n, js_escaped_string}",
|
||||
url_name: "${context_course.location.block_id | n, js_escaped_string}",
|
||||
org: "${context_course.location.org | n, js_escaped_string}",
|
||||
num: "${context_course.location.course | n, js_escaped_string}",
|
||||
display_course_number: "${context_course.display_coursenumber | n, js_escaped_string}",
|
||||
revision: "${context_course.location.branch | n, js_escaped_string}",
|
||||
self_paced: ${context_course.self_paced | n, dump_js_escaped_json}
|
||||
});
|
||||
window.course = new Course(
|
||||
window.pageFactoryArguments['ContextCourse']
|
||||
);
|
||||
% endif
|
||||
% if user.is_authenticated():
|
||||
require(['js/sock']);
|
||||
@@ -145,6 +155,7 @@ from openedx.core.djangolib.markup import HTML
|
||||
});
|
||||
</script>
|
||||
</%block>
|
||||
<%block name="page_bundle_extra"></%block>
|
||||
<%include file="widgets/segment-io-footer.html" />
|
||||
<div class="modal-cover"></div>
|
||||
</body>
|
||||
|
||||
@@ -26,10 +26,11 @@ CMS.URL.TEXTBOOKS = "${textbook_url}"
|
||||
CMS.URL.LMS_BASE = "${settings.LMS_BASE}"
|
||||
</script>
|
||||
</%block>
|
||||
<%block name="requirejs">
|
||||
require(["js/factories/textbooks"], function(TextbooksFactory) {
|
||||
TextbooksFactory(${textbooks | n, dump_js_escaped_json});
|
||||
});
|
||||
|
||||
<%block name="page_bundle_extra">
|
||||
<%static:invoke_page_bundle page_name="js/pages/textbooks" class_name="TextbooksFactory">
|
||||
${textbooks | n, dump_js_escaped_json}
|
||||
</%static:invoke_page_bundle>
|
||||
</%block>
|
||||
|
||||
<%block name="content">
|
||||
@@ -54,7 +55,10 @@ CMS.URL.LMS_BASE = "${settings.LMS_BASE}"
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<article class="content-primary" role="main">
|
||||
|
||||
${static.renderReact(
|
||||
component="NoTextbooks",
|
||||
id="no-textbooks-container"
|
||||
)}
|
||||
</article>
|
||||
<aside class="content-supplementary" role="complementary">
|
||||
<div class="bit">
|
||||
|
||||
Reference in New Issue
Block a user