Commit a4197ede by asadiqbal Committed by Douglas Hall

WL-399 Course Details List Fields

parent f7342186
......@@ -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) {
......
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(
$("<script>", { id: "basic-learning-info-tpl", type: "text/template" }).text(learningInfoTpl)
);
appendSetFixtures(
$("<script>", { id: "basic-instructor-info-tpl", type: "text/template" }).text(instructorInfoTpl)
);
this.model = new CourseDetailsModel(modelData, {parse: true});
this.model = new CourseDetailsModel($.extend(true, {}, modelData, {
instructor_info: {
'instructors': [{"name": "","title": "","organization": "","image": "","bio": ""}]
}}), {parse: true});
this.model.urlRoot = urlRoot;
this.view = new MainView({
el: $('.settings-details'),
......@@ -250,5 +272,85 @@ define([
AjaxHelpers.respondWithJson(requests, expectedJson);
});
it('can add learning information', function () {
this.view.$(SELECTORS.add_course_learning_info).click();
expect('click').not.toHaveBeenPreventedOn(SELECTORS.add_course_learning_info);
expect(this.model.get('learning_info').length).toEqual(2);
this.view.$(SELECTORS.add_course_learning_info).click();
expect(this.model.get('learning_info').length).toEqual(3);
});
it('can delete learning information', function () {
for (var i = 0 ; i < 2; i++) {
this.view.$(SELECTORS.add_course_learning_info).click();
}
expect(this.model.get('learning_info').length).toEqual(3);
expect(this.view.$(SELECTORS.delete_course_learning_info)).toExist();
this.view.$(SELECTORS.delete_course_learning_info).click();
expect(this.model.get('learning_info').length).toEqual(2);
});
it('can save learning information', function () {
expect(this.model.get('learning_info').length).toEqual(1);
var requests = AjaxHelpers.requests(this),
expectedJson = $.extend(true, {}, modelData, {
learning_info: ['testing info']
});
// Input some value.
this.view.$("#course-learning-info-0").val('testing info');
this.view.$("#course-learning-info-0").trigger('change');
this.view.saveView();
AjaxHelpers.expectJsonRequest(
requests, 'POST', urlRoot, expectedJson
);
AjaxHelpers.respondWithJson(requests, expectedJson);
});
it('can add instructor information', function () {
this.view.$(SELECTORS.add_course_instructor_info).click();
expect(this.model.get('instructor_info').instructors.length).toEqual(2);
this.view.$(SELECTORS.add_course_instructor_info).click();
expect(this.model.get('instructor_info').instructors.length).toEqual(3);
});
it('can delete instructor information', function () {
this.view.$(SELECTORS.add_course_instructor_info).click();
expect(this.model.get('instructor_info').instructors.length).toEqual(2);
expect(this.view.$(SELECTORS.remove_instructor_data)).toExist();
this.view.$(SELECTORS.remove_instructor_data).click();
expect(this.model.get('instructor_info').instructors.length).toEqual(1);
});
it('can save instructor information', function () {
var requests = AjaxHelpers.requests(this),
expectedJson = $.extend(true, {}, modelData, {
instructor_info: {
instructors:
[{
"name": "test_name",
"title": "test_title",
"organization": "test_org",
"image": "",
"bio": "test_bio"
}]
}
});
// Input some value.
this.view.$("#course-instructor-name-0").val('test_name').trigger('change');
this.view.$("#course-instructor-title-0").val('test_title').trigger('change');
this.view.$("#course-instructor-organization-0").val('test_org').trigger('change');
this.view.$("#course-instructor-bio-0").val('test_bio').trigger('change');
this.view.saveView();
AjaxHelpers.expectJsonRequest(
requests, 'POST', urlRoot, expectedJson
);
AjaxHelpers.respondWithJson(requests, expectedJson);
});
});
});
// Backbone Application View: Instructor Information
define([ // jshint ignore:line
'jquery',
'underscore',
'backbone',
'gettext',
'js/utils/templates',
"js/models/uploads",
"js/views/uploads"
],
function ($, _, Backbone, gettext, TemplateUtils, FileUploadModel, FileUploadDialog) {
'use strict';
var InstructorInfoView = Backbone.View.extend({
events : {
'click .remove-instructor-data': 'removeInstructor',
'click .action-upload-instructor-image': "uploadInstructorImage"
},
initialize: function() {
// Set up the initial state of the attributes set for this model instance
_.bindAll(this, 'render');
this.template = this.loadTemplate('course-instructor-details');
this.listenTo(this.model, 'change:instructor_info', this.render);
},
loadTemplate: function(name) {
// Retrieve the corresponding template for this model
return TemplateUtils.loadTemplate(name);
},
render: function() {
// Assemble the render view for this model.
$(".course-instructor-details-fields").empty();
var self = this;
$.each(this.model.get('instructor_info').instructors, function( index, data ) {
$(self.el).append(self.template({
data: data,
index: index
}));
});
// Avoid showing broken image on mistyped/nonexistent image
this.$el.find('img').error(function() {
$(this).hide();
});
this.$el.find('img').load(function() {
$(this).show();
});
},
removeInstructor: function(event) {
/*
* Remove course Instructor fields.
* */
event.preventDefault();
var index = event.currentTarget.getAttribute('data-index'),
instructors = this.model.get('instructor_info').instructors.slice(0);
instructors.splice(index, 1);
this.model.set('instructor_info', {instructors: instructors});
},
uploadInstructorImage: function(event) {
/*
* Upload instructor image.
* */
event.preventDefault();
var index = event.currentTarget.getAttribute('data-index'),
instructors = this.model.get('instructor_info').instructors.slice(0),
instructor = instructors[index];
var upload = new FileUploadModel({
title: gettext("Upload instructor image."),
message: gettext("Files must be in JPEG or PNG format."),
mimeTypes: ['image/jpeg', 'image/png']
});
var self = this;
var modal = new FileUploadDialog({
model: upload,
onSuccess: function(response) {
instructor.image = response.asset.url;
self.model.set('instructor_info', {instructors: instructors});
self.model.trigger('change', self.model);
self.model.trigger('change:instructor_info', self.model);
}
});
modal.show();
}
});
return InstructorInfoView;
});
// Backbone Application View: Course Learning Information
define([ // jshint ignore:line
'jquery',
'underscore',
'backbone',
'gettext',
'js/utils/templates'
],
function ($, _, Backbone, gettext, TemplateUtils) {
'use strict';
var LearningInfoView = Backbone.View.extend({
events: {
'click .delete-course-learning-info': "removeLearningInfo"
},
initialize: function() {
// Set up the initial state of the attributes set for this model instance
_.bindAll(this, 'render');
this.template = this.loadTemplate('course-settings-learning-fields');
this.listenTo(this.model, 'change:learning_info', this.render);
},
loadTemplate: function(name) {
// Retrieve the corresponding template for this model
return TemplateUtils.loadTemplate(name);
},
render: function() {
// rendering for this model
$("li.course-settings-learning-fields").empty();
var self = this;
var learning_information = this.model.get('learning_info');
$.each(learning_information, function( index, info ) {
$(self.el).append(self.template({index: index, info: info, info_count: learning_information.length }));
});
},
removeLearningInfo: function(event) {
/*
* Remove course learning fields.
* */
event.preventDefault();
var index = event.currentTarget.getAttribute('data-index'),
existing_info = _.clone(this.model.get('learning_info'));
existing_info.splice(index, 1);
this.model.set('learning_info', existing_info);
}
});
return LearningInfoView;
});
define(["js/views/validation", "codemirror", "underscore", "jquery", "jquery.ui", "js/utils/date_utils", "js/models/uploads",
"js/views/uploads", "js/views/license", "js/models/license",
define(["js/views/validation", "codemirror", "underscore", "jquery", "jquery.ui", "js/utils/date_utils",
"js/models/uploads", "js/views/uploads", "js/views/license", "js/models/license",
"common/js/components/views/feedback_notification", "jquery.timepicker", "date", "gettext",
'edx-ui-toolkit/js/utils/string-utils'],
"js/views/learning_info", "js/views/instructor_info", "edx-ui-toolkit/js/utils/string-utils"],
function(ValidatingView, CodeMirror, _, $, ui, DateUtils, FileUploadModel,
FileUploadDialog, LicenseView, LicenseModel, NotificationView,
timepicker, date, gettext, StringUtils) {
timepicker, date, gettext, LearningInfoView, InstructorInfoView, StringUtils) {
var DetailsView = ValidatingView.extend({
// Model class is CMS.Models.Settings.CourseDetails
......@@ -21,7 +21,9 @@ var DetailsView = ValidatingView.extend({
// would love to move to a general superclass, but event hashes don't inherit in backbone :-(
'focus :input' : "inputFocus",
'blur :input' : "inputUnfocus",
'click .action-upload-image': "uploadImage"
'click .action-upload-image': "uploadImage",
'click .add-course-learning-info': "addLearningFields",
'click .add-course-instructor-info': "addInstructorFields"
},
initialize : function(options) {
......@@ -60,6 +62,16 @@ var DetailsView = ValidatingView.extend({
closeIcon: true
}).show();
}
this.learning_info_view = new LearningInfoView({
el: $(".course-settings-learning-fields"),
model: this.model
});
this.instructor_info_view = new InstructorInfoView({
el: $(".course-instructor-details-fields"),
model: this.model
});
},
render: function() {
......@@ -134,6 +146,8 @@ var DetailsView = ValidatingView.extend({
}
this.licenseView.render();
this.learning_info_view.render();
this.instructor_info_view.render();
return this;
},
......@@ -156,7 +170,35 @@ var DetailsView = ValidatingView.extend({
'video_thumbnail_image_asset_path': 'video-thumbnail-image-url',
'pre_requisite_courses': 'pre-requisite-course',
'entrance_exam_enabled': 'entrance-exam-enabled',
'entrance_exam_minimum_score_pct': 'entrance-exam-minimum-score-pct'
'entrance_exam_minimum_score_pct': 'entrance-exam-minimum-score-pct',
'course_settings_learning_fields': 'course-settings-learning-fields',
'add_course_learning_info': 'add-course-learning-info',
'add_course_instructor_info': 'add-course-instructor-info',
'course_learning_info': 'course-learning-info'
},
addLearningFields: function() {
/*
* Add new course learning fields.
* */
var existingInfo = _.clone(this.model.get('learning_info'));
existingInfo.push('');
this.model.set('learning_info', existingInfo);
},
addInstructorFields: function() {
/*
* Add new course instructor fields.
* */
var instructors = this.model.get('instructor_info').instructors.slice(0);
instructors.push({
name: '',
title: '',
organization: '',
image: '',
bio: ''
});
this.model.set('instructor_info', {instructors: instructors});
},
updateTime : function(e) {
......@@ -174,14 +216,33 @@ var DetailsView = ValidatingView.extend({
$(e.currentTarget).attr('title', currentTimeText);
},
updateModel: function(event) {
var value;
var index = event.currentTarget.getAttribute('data-index');
switch (event.currentTarget.id) {
case 'course-learning-info-' + index:
value = $(event.currentTarget).val();
var learningInfo = this.model.get('learning_info');
learningInfo[index] = value;
this.showNotificationBar();
break;
case 'course-instructor-name-' + index:
case 'course-instructor-title-' + index:
case 'course-instructor-organization-' + index:
case 'course-instructor-bio-' + index:
value = $(event.currentTarget).val();
var field = event.currentTarget.getAttribute('data-field'),
instructors = this.model.get('instructor_info').instructors.slice(0);
instructors[index][field] = value;
this.model.set('instructor_info', {instructors: instructors});
this.showNotificationBar();
break;
case 'course-image-url':
this.updateImageField(event, 'course_image_name', '#course-image');
break;
case 'banner-image-url':
this.updateImageField(event, 'banner_image_name', '#banner-image');
break;
case 'video-thumbnail-image-url':
case 'video-thumbnail-image-url':
this.updateImageField(event, 'video_thumbnail_image_name', '#video-thumbnail-image');
break;
case 'entrance-exam-enabled':
......
......@@ -838,6 +838,142 @@
}
}
&.course-learning-info {
.list-input {
margin-bottom: $baseline;
.course-settings-learning-fields {
.field {
.input-learning-info {
width: flex-grid(10, 12);
display: inline-block;
}
}
}
}
.actions {
width: flex-grid(9, 9);
.new-button {
@extend %btn-primary-green;
}
.delete-button {
margin: 0;
}
}
}
&.instructor-types {
.list-input {
.course-instructor-details-fields {
.field {
width: flex-grid(2, 6);
&.field-course-instructor-bio {
width: flex-grid(6, 6);
}
&.current-instructor-image {
width: flex-grid(6, 6);
text-align: left;
padding: 0;
.wrapper-instructor-image {
margin: 15px auto;
}
}
}
}
&:last-child {
margin-bottom: 0;
}
}
.field-group {
@include clearfix();
width: flex-grid(9, 9);
margin-bottom: ($baseline*1.5);
border-bottom: 1px solid $gray-l5;
padding-bottom: ($baseline*1.5);
&:last-child {
border: none;
padding-bottom: 0;
}
.field {
display: inline-block;
vertical-align: top;
width: flex-grid(3, 6);
margin-bottom: ($baseline/2);
margin-right: flex-gutter();
}
// specific fields - course image
.field-course-instructor-image {
margin-bottom: ($baseline/2);
padding: ($baseline/2) $baseline;
background: $gray-l5;
text-align: left;
.wrapper-instructor-image {
display: block;
width: 375px;
height: 200px;
overflow: hidden;
margin: 0 auto;
border: 1px solid $gray-l4;
box-shadow: 0 1px 1px $shadow-l1;
padding: ($baseline/2);
background: $white;
}
.instructor-image {
display: block;
width: 100%;
min-height: 100%;
}
.msg {
@extend %t-copy-sub2;
display: block;
margin-top: ($baseline/2);
color: $gray-l3;
}
}
.wrapper-input {
@include clearfix();
width: flex-grid(9,9);
.input {
float: left;
width: flex-grid(6,9);
margin-right: flex-gutter();
}
.action-upload-instructor-image {
@extend %ui-btn-flat-outline;
float: right;
width: flex-grid(2,9);
margin-top: ($baseline/4);
padding: ($baseline/2) $baseline;
}
}
}
.actions {
width: flex-grid(9, 9);
.new-button {
@extend %btn-primary-green;
}
.delete-button {
margin: 0;
}
}
}
// specific fields - advanced settings
&.advanced-policies {
......
<li class="field-group">
<div class="field text field-course-instructor-name">
<label for="course-instructor-name-<%- index %>"><%- gettext("Name") %></label>
<input type="text" class="long" id="course-instructor-name-<%- index %>" value="<%- data['name'] %>" data-index=<%- index %> data-field="name" placeholder="<%- gettext('Instructor Name') %>" />
<span class="tip tip-stacked"><%- gettext("Please add the instructor's name")%></span>
</div>
<div class="field text field-course-instructor-title">
<label for="course-instructor-title-<%- index %>"><%- gettext("Title") %></label>
<input type="text" class="long" id="course-instructor-title-<%- index %>" value="<%- data['title'] %>" data-index=<%- index %> data-field="title" placeholder="<%- gettext('Instructor Title') %>" />
<span class="tip tip-stacked"><%- gettext("Please add the instructor's title")%></span>
</div>
<div class="field text field-course-instructor-organization">
<label for="course-instructor-organization-<%- index %>"><%- gettext("Organization") %></label>
<input type="text" class="long" id="course-instructor-organization-<%- index %>" value = "<%- data['organization'] %>" data-index=<%- index %> data-field="organization" placeholder="<%- gettext('Organization Name') %>" />
<span class="tip tip-stacked"><%- gettext("Please add the institute where the instructor is associated")%></span>
</div>
<div class="field text field-course-instructor-bio">
<label for="course-instructor-bio-<%- index %>"><%- gettext("Biography") %></label>
<textarea class="short text" id="course-instructor-bio-<%- index %>" data-index=<%- index %> data-field="bio" placeholder="<%- gettext('Instructor Biography') %>" ><%- data['bio'] %></textarea>
<span class="tip tip-stacked"><%- gettext("Please add the instructor's biography")%></span>
</div>
<div class="field image field-course-instructor-image current-instructor-image">
<label for="course-instructor-image-<%- index %>"><%- gettext("Photo") %></label>
<span class="wrapper-instructor-image">
<img class="instructor-image" src="<%- data['image']%>" alt="<%- gettext('Instructor Photo') %>" />
</span>
<div class="wrapper-input">
<div class="input">
<input type="text" dir="ltr" class="long new-instructor-image-url" id="course-instructor-image-<%- index %>" value="<%- data['image'] %>" data-field="image" placeholder="<%- gettext('Instructor Photo URL') %>" autocomplete="off" />
<span class="tip tip-stacked"><%- gettext("Please add a photo of the instructor (Note: only JPEG or PNG format supported)")%></span>
</div>
<button type="button" class="action action-upload-instructor-image" data-index=<%- index %>><%- gettext("Upload Photo") %></button>
</div>
</div>
<div class="actions">
<button type="button" class="button delete-button standard remove-item remove-instructor-data" data-index=<%- index %>><%- gettext("Delete") %></button>
</div>
</li>
<div class="field text" id="fields-course-learning-info-<%- index %>">
<label for= "course-learning-info-<%- index %>"><%- gettext("Learning Outcome") %> <%- index + 1 %></label>
<input type="text" class="input-learning-info" id="course-learning-info-<%- index %>" value="<%- info %>" data-index="<%- index %>" placeholder="<%- gettext('Add a learning outcome here') %>">
<button type="button" class="button delete-button standard remove-item delete-course-learning-info" data-index="<%- index %>"><%- gettext("Delete") %></button>
</div>
......@@ -197,4 +197,33 @@
</ol>
</section>
<section class="group-settings course-learning-info">
<header>
<h2 class="title-2">Learning Outcomes</h2>
<span class="tip">Add the learning outcomes for this course</span>
</header>
<ol class="list-input enum">
<li class="course-settings-learning-fields"></li>
</ol>
<div class="actions">
<button type="button" class="action action-primary button new-button add-course-learning-info">
<i class="icon fa fa-plus icon-inline"></i>Add Learning Outcome
</button>
</div>
</section>
<section class="group-settings instructor-types">
<header>
<h2 class="title-2">Instructors</h2>
<span class="tip">Add details about the instructors for this course</span>
</header>
<ol class="list-input enum">
<li class="course-instructor-details-fields"></li>
</ol>
<div class="actions">
<button type="button" class="action action-primary button new-button add-course-instructor-info">
<i class="icon fa fa-plus icon-inline"></i>Add Instructor
</button>
</div>
</section>
</form>
......@@ -14,7 +14,7 @@
%>
<%block name="header_extras">
% for template_name in ["basic-modal", "modal-button", "upload-dialog", "license-selector"]:
% for template_name in ["basic-modal", "modal-button", "upload-dialog", "license-selector", "course-settings-learning-fields", "course-instructor-details"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="js/${template_name}.underscore" />
</script>
......@@ -448,10 +448,44 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url}'
<span class="tip tip-stacked">${_("Enter your YouTube video's ID (along with any restriction parameters)")}</span>
</div>
</li>
% endif
% endif
</ol>
</section>
% if enable_extended_course_details:
<hr class="divide" />
<section class="group-settings course-learning-info">
<header>
<h2 class="title-2">${_("Learning Outcomes")}</h2>
<span class="tip">${_("Add the learning outcomes for this course")}</span>
</header>
<ol class="list-input enum">
<li class="course-settings-learning-fields"></li>
</ol>
<div class="actions">
<button type="button" class="action action-primary button new-button add-course-learning-info">
<i class="icon fa fa-plus icon-inline"></i>${_("Add Learning Outcome")}
</button>
</div>
</section>
<hr class="divide" />
<section class="group-settings instructor-types">
<header>
<h2 class="title-2">${_("Instructors")}</h2>
<span class="tip">${_("Add details about the instructors for this course")}</span>
</header>
<ol class="list-input enum">
<li class="course-instructor-details-fields"></li>
</ol>
<div class="actions">
<button type="button" class="action action-primary button new-button add-course-instructor-info">
<i class="icon fa fa-plus icon-inline"></i>${_("Add Instructor")}
</button>
</div>
</section>
% endif
% if about_page_editable or is_prerequisite_courses_enabled or is_entrance_exams_enabled:
<hr class="divide" />
......
......@@ -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
"""
......
......@@ -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')
......
......@@ -219,4 +219,6 @@ class AdvancedSettingsPage(CoursePage):
'enable_proctored_exams',
'enable_timed_exams',
'enable_subsection_gating',
'learning_info',
'instructor_info'
]
......@@ -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'))
......@@ -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
......
......@@ -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()
......
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