diff --git a/cms/static/js/models/settings/course_details.js b/cms/static/js/models/settings/course_details.js
index 2e192e066b..202d1097e7 100644
--- a/cms/static/js/models/settings/course_details.js
+++ b/cms/static/js/models/settings/course_details.js
@@ -29,7 +29,9 @@ var CourseDetails = Backbone.Model.extend({
video_thumbnail_image_asset_path: '',
pre_requisite_courses: [],
entrance_exam_enabled : '',
- entrance_exam_minimum_score_pct: '50'
+ entrance_exam_minimum_score_pct: '50',
+ learning_info: [],
+ instructor_info: {}
},
validate: function(newattrs) {
diff --git a/cms/static/js/spec/views/settings/main_spec.js b/cms/static/js/spec/views/settings/main_spec.js
index 5c686f4ac9..e87d706c9e 100644
--- a/cms/static/js/spec/views/settings/main_spec.js
+++ b/cms/static/js/spec/views/settings/main_spec.js
@@ -1,13 +1,17 @@
define([
'jquery', 'js/models/settings/course_details', 'js/views/settings/main',
- 'common/js/spec_helpers/ajax_helpers'
-], function($, CourseDetailsModel, MainView, AjaxHelpers) {
+ 'common/js/spec_helpers/ajax_helpers', 'common/js/spec_helpers/template_helpers',
+], function($, CourseDetailsModel, MainView, AjaxHelpers, TemplateHelpers) {
'use strict';
var SELECTORS = {
entrance_exam_min_score: '#entrance-exam-minimum-score-pct',
entrance_exam_enabled_field: '#entrance-exam-enabled',
- grade_requirement_div: '.div-grade-requirements div'
+ grade_requirement_div: '.div-grade-requirements div',
+ add_course_learning_info: '.add-course-learning-info',
+ delete_course_learning_info: '.delete-course-learning-info',
+ add_course_instructor_info: '.add-course-instructor-info',
+ remove_instructor_data: '.remove-instructor-data'
};
describe('Settings/Main', function () {
@@ -39,14 +43,32 @@ define([
entrance_exam_enabled : '',
entrance_exam_minimum_score_pct: '50',
license: null,
- language: ''
+ language: '',
+ learning_info: [''],
+ instructor_info: {
+ 'instructors': [{"name": "","title": "","organization": "","image": "","bio": ""}]
+ }
},
- mockSettingsPage = readFixtures('mock/mock-settings-page.underscore');
+
+ mockSettingsPage = readFixtures('mock/mock-settings-page.underscore'),
+ learningInfoTpl = readFixtures('course-settings-learning-fields.underscore'),
+ instructorInfoTpl = readFixtures('course-instructor-details.underscore');
beforeEach(function () {
- setFixtures(mockSettingsPage);
+ TemplateHelpers.installTemplates(['course-settings-learning-fields', 'course-instructor-details'], true);
+ appendSetFixtures(mockSettingsPage);
+ appendSetFixtures(
+ $("
@@ -448,10 +448,44 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url}'
${_("Enter your YouTube video's ID (along with any restriction parameters)")}
- % endif
+ % endif
+ % if enable_extended_course_details:
+
+
+
+ ${_("Learning Outcomes")}
+ ${_("Add the learning outcomes for this course")}
+
+
+
+
+
+
+
+
+
+
+
+
+ ${_("Instructors")}
+ ${_("Add details about the instructors for this course")}
+
+
+
+
+
+
+
+
+ % endif
+
% if about_page_editable or is_prerequisite_courses_enabled or is_entrance_exams_enabled:
diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py
index c24444b825..85188e7ddb 100644
--- a/common/lib/xmodule/xmodule/course_module.py
+++ b/common/lib/xmodule/xmodule/course_module.py
@@ -794,6 +794,36 @@ class CourseFields(object):
scope=Scope.settings
)
+ learning_info = List(
+ display_name=_("Course Learning Information"),
+ help=_("Specify what student can learn from the course."),
+ default=[],
+ scope=Scope.settings
+ )
+
+ """
+ instructor_info dict structure:
+ {
+ "instructors": [
+ {
+ "name": "",
+ "title": "",
+ "organization": "",
+ "image": "",
+ "bio": ""
+ }
+ ]
+ }
+ """
+ instructor_info = Dict(
+ display_name=_("Course Instructor"),
+ help=_("Enter the details for Course Instructor"),
+ default={
+ "instructors": []
+ },
+ scope=Scope.settings
+ )
+
class CourseModule(CourseFields, SequenceModule): # pylint: disable=abstract-method
"""
diff --git a/common/test/acceptance/pages/studio/settings.py b/common/test/acceptance/pages/studio/settings.py
index eab10069cb..9546de0c2b 100644
--- a/common/test/acceptance/pages/studio/settings.py
+++ b/common/test/acceptance/pages/studio/settings.py
@@ -261,12 +261,15 @@ class SettingsPage(CoursePage):
# Return the joined path of the required asset.
return os.sep.join(folders_list_in_path)
- def upload_image(self, image_selector, file_to_upload):
+ def upload_image(self, upload_btn_selector, file_to_upload):
"""
Upload image specified by image_selector and file_to_upload
"""
- self.q(css=image_selector).results[0].click()
+ # wait for upload button
+ self.wait_for_element_presence(upload_btn_selector, 'upload button is present')
+
+ self.q(css=upload_btn_selector).results[0].click()
# wait for popup
self.wait_for_element_presence(self.upload_image_popup_window_selector, 'upload dialog is present')
diff --git a/common/test/acceptance/pages/studio/settings_advanced.py b/common/test/acceptance/pages/studio/settings_advanced.py
index 7f189a4cd9..87acaf1785 100644
--- a/common/test/acceptance/pages/studio/settings_advanced.py
+++ b/common/test/acceptance/pages/studio/settings_advanced.py
@@ -219,4 +219,6 @@ class AdvancedSettingsPage(CoursePage):
'enable_proctored_exams',
'enable_timed_exams',
'enable_subsection_gating',
+ 'learning_info',
+ 'instructor_info'
]
diff --git a/openedx/core/djangoapps/models/course_details.py b/openedx/core/djangoapps/models/course_details.py
index c818642def..0db50a3803 100644
--- a/openedx/core/djangoapps/models/course_details.py
+++ b/openedx/core/djangoapps/models/course_details.py
@@ -70,6 +70,8 @@ class CourseDetails(object):
'50'
) # minimum passing score for entrance exam content module/tree,
self.self_paced = None
+ self.learning_info = []
+ self.instructor_info = []
@classmethod
def fetch_about_attribute(cls, course_key, attribute):
@@ -108,6 +110,8 @@ class CourseDetails(object):
course_details.video_thumbnail_image_asset_path = course_image_url(descriptor, 'video_thumbnail_image')
course_details.language = descriptor.language
course_details.self_paced = descriptor.self_paced
+ course_details.learning_info = descriptor.learning_info
+ course_details.instructor_info = descriptor.instructor_info
# Default course license is "All Rights Reserved"
course_details.license = getattr(descriptor, "license", "all-rights-reserved")
@@ -243,6 +247,14 @@ class CourseDetails(object):
descriptor.license = jsondict['license']
dirty = True
+ if 'learning_info' in jsondict:
+ descriptor.learning_info = jsondict['learning_info']
+ dirty = True
+
+ if 'instructor_info' in jsondict:
+ descriptor.instructor_info = jsondict['instructor_info']
+ dirty = True
+
if 'language' in jsondict and jsondict['language'] != descriptor.language:
descriptor.language = jsondict['language']
dirty = True
diff --git a/openedx/core/djangoapps/models/tests/test_course_details.py b/openedx/core/djangoapps/models/tests/test_course_details.py
index fd778ee10a..661ddfe456 100644
--- a/openedx/core/djangoapps/models/tests/test_course_details.py
+++ b/openedx/core/djangoapps/models/tests/test_course_details.py
@@ -110,6 +110,26 @@ class CourseDetailsTestCase(ModuleStoreTestCase):
CourseDetails.update_from_json(self.course.id, jsondetails.__dict__, self.user).language,
jsondetails.language
)
+ jsondetails.learning_info = ["test", "test"]
+ self.assertEqual(
+ CourseDetails.update_from_json(self.course.id, jsondetails.__dict__, self.user).learning_info,
+ jsondetails.learning_info
+ )
+ jsondetails.instructor_info = {
+ "instructors": [
+ {
+ "name": "test",
+ "title": "test",
+ "organization": "test",
+ "image": "test",
+ "bio": "test"
+ }
+ ]
+ }
+ self.assertEqual(
+ CourseDetails.update_from_json(self.course.id, jsondetails.__dict__, self.user).instructor_info,
+ jsondetails.instructor_info
+ )
def test_toggle_pacing_during_course_run(self):
SelfPacedConfiguration(enabled=True).save()