From a4197eded54d7f30fc5b2a230d226988ed8d2446 Mon Sep 17 00:00:00 2001 From: asadiqbal Date: Wed, 20 Apr 2016 20:44:11 +0500 Subject: [PATCH] WL-399 Course Details List Fields --- .../js/models/settings/course_details.js | 4 +- .../js/spec/views/settings/main_spec.js | 116 ++++++++++++++- cms/static/js/views/instructor_info.js | 92 ++++++++++++ cms/static/js/views/learning_info.js | 53 +++++++ cms/static/js/views/settings/main.js | 75 +++++++++- cms/static/sass/views/_settings.scss | 136 ++++++++++++++++++ .../js/course-instructor-details.underscore | 43 ++++++ ...course-settings-learning-fields.underscore | 5 + .../js/mock/mock-settings-page.underscore | 29 ++++ cms/templates/settings.html | 38 ++++- common/lib/xmodule/xmodule/course_module.py | 30 ++++ .../test/acceptance/pages/studio/settings.py | 7 +- .../pages/studio/settings_advanced.py | 2 + .../tests/studio/test_studio_settings.py | 39 ----- .../core/djangoapps/models/course_details.py | 12 ++ .../models/tests/test_course_details.py | 20 +++ 16 files changed, 643 insertions(+), 58 deletions(-) create mode 100644 cms/static/js/views/instructor_info.js create mode 100644 cms/static/js/views/learning_info.js create mode 100644 cms/templates/js/course-instructor-details.underscore create mode 100644 cms/templates/js/course-settings-learning-fields.underscore 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")} +
+
    +
  1. +
+
+ +
+
+ +
+
+
+

${_("Instructors")}

+ ${_("Add details about the instructors for this course")} +
+
    +
  1. +
+
+ +
+
+ % 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 6ed3385032..136d2f963b 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 adb012f95d..da8b8a3d26 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/common/test/acceptance/tests/studio/test_studio_settings.py b/common/test/acceptance/tests/studio/test_studio_settings.py index 9b67b5dcd4..3e63ee7167 100644 --- a/common/test/acceptance/tests/studio/test_studio_settings.py +++ b/common/test/acceptance/tests/studio/test_studio_settings.py @@ -571,42 +571,3 @@ class StudioSubsectionSettingsA11yTest(StudioCourseTest): include=['section.edit-settings-timed-examination'] ) self.course_outline.a11y_audit.check_for_accessibility_errors() - - -class StudioSettingsImageUploadTest(StudioCourseTest): - - """ - Class to test course settings image uploads. - """ - - def setUp(self): # pylint: disable=arguments-differ - super(StudioSettingsImageUploadTest, self).setUp() - self.settings_page = SettingsPage(self.browser, self.course_info['org'], self.course_info['number'], - self.course_info['run']) - - def test_upload_course_card_image(self): - self.settings_page.visit() - self.settings_page.wait_for_page() - - # upload image - file_to_upload = 'image.jpg' - self.settings_page.upload_image('#upload-course-image', file_to_upload) - self.assertIn(file_to_upload, self.settings_page.get_uploaded_image_path('#course-image')) - - def test_upload_course_banner_image(self): - self.settings_page.visit() - self.settings_page.wait_for_page() - - # upload image - file_to_upload = 'image.jpg' - self.settings_page.upload_image('#upload-banner-image', file_to_upload) - self.assertIn(file_to_upload, self.settings_page.get_uploaded_image_path('#banner-image')) - - def test_upload_course_video_thumbnail_image(self): - self.settings_page.visit() - self.settings_page.wait_for_page() - - # upload image - file_to_upload = 'image.jpg' - self.settings_page.upload_image('#upload-video-thumbnail-image', file_to_upload) - self.assertIn(file_to_upload, self.settings_page.get_uploaded_image_path('#video-thumbnail-image')) 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()