diff --git a/cms/djangoapps/models/settings/course_metadata.py b/cms/djangoapps/models/settings/course_metadata.py
index 988feed9f6..0dbf338218 100644
--- a/cms/djangoapps/models/settings/course_metadata.py
+++ b/cms/djangoapps/models/settings/course_metadata.py
@@ -41,7 +41,6 @@ class CourseMetadata:
'enrollment_start',
'enrollment_end',
'certificate_available_date',
- 'certificates_display_behavior',
'tabs',
'graceperiod',
'show_timezone',
diff --git a/cms/static/js/factories/settings.js b/cms/static/js/factories/settings.js
index 4e399215ca..6ed11c4916 100644
--- a/cms/static/js/factories/settings.js
+++ b/cms/static/js/factories/settings.js
@@ -13,12 +13,6 @@ define([
$('label').removeClass('is-focused');
});
- // Toggle collapsibles when trigger is clicked
- $(".collapsible .collapsible-trigger").click(function() {
- const contentId = this.id.replace("-trigger", "-content")
- $(`#${contentId}`).toggleClass("collapsed")
- })
-
model = new CourseDetailsModel();
model.urlRoot = detailsUrl;
model.showCertificateAvailableDate = showCertificateAvailableDate;
diff --git a/cms/static/js/models/settings/course_details.js b/cms/static/js/models/settings/course_details.js
index 22c3f462a3..c0d8c506f4 100644
--- a/cms/static/js/models/settings/course_details.js
+++ b/cms/static/js/models/settings/course_details.js
@@ -1,7 +1,7 @@
define(['backbone', 'underscore', 'gettext', 'js/models/validation_helpers', 'js/utils/date_utils',
'edx-ui-toolkit/js/utils/string-utils'
],
- function (Backbone, _, gettext, ValidationHelpers, DateUtils, StringUtils) {
+ function(Backbone, _, gettext, ValidationHelpers, DateUtils, StringUtils) {
'use strict';
var CourseDetails = Backbone.Model.extend({
defaults: {
@@ -11,7 +11,6 @@ define(['backbone', 'underscore', 'gettext', 'js/models/validation_helpers', 'js
language: '',
start_date: null, // maps to 'start'
end_date: null, // maps to 'end'
- certificates_display_behavior: "",
certificate_available_date: null,
enrollment_start: null,
enrollment_end: null,
@@ -39,16 +38,10 @@ define(['backbone', 'underscore', 'gettext', 'js/models/validation_helpers', 'js
self_paced: null
},
- validate: function (newattrs) {
- // Returns either nothing (no return call) so that validate works or an object of {field: errorstring} pairs
- // A bit funny in that the video key validation is asynchronous; so, it won't stop the validation.
+ validate: function(newattrs) {
+ // Returns either nothing (no return call) so that validate works or an object of {field: errorstring} pairs
+ // A bit funny in that the video key validation is asynchronous; so, it won't stop the validation.
var errors = {};
- const CERTIFICATES_DISPLAY_BEHAVIOR_OPTIONS = {
- END: "end",
- END_WITH_DATE: "end_with_date",
- EARLY_NO_INFO: "early_no_info"
- };
-
newattrs = DateUtils.convertDateStringsToObjects(
newattrs,
['start_date', 'end_date', 'certificate_available_date', 'enrollment_start', 'enrollment_end']
@@ -62,15 +55,15 @@ define(['backbone', 'underscore', 'gettext', 'js/models/validation_helpers', 'js
errors.end_date = gettext('The course end date must be later than the course start date.');
}
if (newattrs.start_date && newattrs.enrollment_start &&
- newattrs.start_date < newattrs.enrollment_start) {
+ newattrs.start_date < newattrs.enrollment_start) {
errors.enrollment_start = gettext(
- 'The course start date must be later than the enrollment start date.'
+ 'The course start date must be later than the enrollment start date.'
);
}
if (newattrs.enrollment_start && newattrs.enrollment_end &&
- newattrs.enrollment_start >= newattrs.enrollment_end) {
+ newattrs.enrollment_start >= newattrs.enrollment_end) {
errors.enrollment_end = gettext(
- 'The enrollment start date cannot be after the enrollment end date.'
+ 'The enrollment start date cannot be after the enrollment end date.'
);
}
if (newattrs.end_date && newattrs.enrollment_end && newattrs.end_date < newattrs.enrollment_end) {
@@ -82,43 +75,11 @@ define(['backbone', 'underscore', 'gettext', 'js/models/validation_helpers', 'js
'The certificate available date must be later than the course end date.'
);
}
-
-
- if (
- newattrs.certificates_display_behavior
- && !(Object.values(CERTIFICATES_DISPLAY_BEHAVIOR_OPTIONS).includes(newattrs.certificates_display_behavior))
- ) {
-
- errors.certificates_display_behavior = StringUtils.interpolate(
- gettext(
- "The certificate display behavior must be one of: {behavior_options}"
- ),
- {
- behavior_options: Object.values(CERTIFICATES_DISPLAY_BEHAVIOR_OPTIONS).join(', ')
- }
- );
- }
-
- // Throw error if there's a value for certificate_available_date
- if(
- (newattrs.certificate_available_date && newattrs.certificates_display_behavior != CERTIFICATES_DISPLAY_BEHAVIOR_OPTIONS.END_WITH_DATE)
- || (!newattrs.certificate_available_date && newattrs.certificates_display_behavior == CERTIFICATES_DISPLAY_BEHAVIOR_OPTIONS.END_WITH_DATE)
- ){
- errors.certificates_display_behavior = StringUtils.interpolate(
- gettext(
- "The certificates display behavior must be {valid_option} if certificate available date is set."
- ),
- {
- valid_option: CERTIFICATES_DISPLAY_BEHAVIOR_OPTIONS.END_WITH_DATE
- }
- );
- }
-
if (newattrs.intro_video && newattrs.intro_video !== this.get('intro_video')) {
if (this._videokey_illegal_chars.exec(newattrs.intro_video)) {
errors.intro_video = gettext('Key should only contain letters, numbers, _, or -');
}
- // TODO check if key points to a real video using google's youtube api
+ // TODO check if key points to a real video using google's youtube api
}
if (_.has(newattrs, 'entrance_exam_minimum_score_pct')) {
var range = {
@@ -127,37 +88,37 @@ define(['backbone', 'underscore', 'gettext', 'js/models/validation_helpers', 'js
};
if (!ValidationHelpers.validateIntegerRange(newattrs.entrance_exam_minimum_score_pct, range)) {
errors.entrance_exam_minimum_score_pct = StringUtils.interpolate(gettext(
- 'Please enter an integer between %(min)s and %(max)s.'
+ 'Please enter an integer between %(min)s and %(max)s.'
), range, true);
}
}
if (!_.isEmpty(errors)) return errors;
- // NOTE don't return empty errors as that will be interpreted as an error state
+ // NOTE don't return empty errors as that will be interpreted as an error state
},
_videokey_illegal_chars: /[^a-zA-Z0-9_-]/g,
- set_videosource: function (newsource) {
- // newsource either is or just the "speed:key, *" string
- // returns the videosource for the preview which iss the key whose speed is closest to 1
+ set_videosource: function(newsource) {
+ // newsource either is or just the "speed:key, *" string
+ // returns the videosource for the preview which iss the key whose speed is closest to 1
if (_.isEmpty(newsource) &&
- !_.isEmpty(this.get('intro_video'))) this.set({ intro_video: null }, { validate: true });
- // TODO remove all whitespace w/in string
+ !_.isEmpty(this.get('intro_video'))) this.set({intro_video: null}, {validate: true});
+ // TODO remove all whitespace w/in string
else {
- if (this.get('intro_video') !== newsource) this.set('intro_video', newsource, { validate: true });
+ if (this.get('intro_video') !== newsource) this.set('intro_video', newsource, {validate: true});
}
return this.videosourceSample();
},
- videosourceSample: function () {
+ videosourceSample: function() {
if (this.has('intro_video')) return '//www.youtube.com/embed/' + this.get('intro_video');
else return '';
},
- // Whether or not the course pacing can be toggled. If the course
- // has already started, returns false; otherwise, returns true.
- canTogglePace: function () {
+ // Whether or not the course pacing can be toggled. If the course
+ // has already started, returns false; otherwise, returns true.
+ canTogglePace: function() {
return new Date() <= new Date(this.get('start_date'));
}
});
diff --git a/cms/static/js/spec/views/settings/main_spec.js b/cms/static/js/spec/views/settings/main_spec.js
index ce134ff0c4..f378879fb1 100644
--- a/cms/static/js/spec/views/settings/main_spec.js
+++ b/cms/static/js/spec/views/settings/main_spec.js
@@ -21,8 +21,7 @@ define([
end_date: '2014-11-05T20:00:00Z',
enrollment_start: '2014-10-00T00:00:00Z',
enrollment_end: '2014-11-05T00:00:00Z',
- certificates_display_behavior: 'end',
- certificate_available_date: null,
+ certificate_available_date: '2014-11-05T20:00:00Z',
org: '',
course_id: '',
run: '',
@@ -352,8 +351,6 @@ define([
});
it('should disallow save with a certificate available date before end date', function() {
this.model.showCertificateAvailableDate = true;
- $('#certificates-display-behavior').val('end_with_date').trigger('change');
- $('#certificate-available-date').val('01/01/2020').trigger('change');
$('#course-end-date').val('01/01/2030').trigger('change');
expect(this.view.$('.message-error')).toExist();
});
diff --git a/cms/static/js/views/settings/main.js b/cms/static/js/views/settings/main.js
index 70a071d080..03ddc9ace1 100644
--- a/cms/static/js/views/settings/main.js
+++ b/cms/static/js/views/settings/main.js
@@ -2,503 +2,469 @@ define(['js/views/validation', 'codemirror', 'underscore', 'jquery', 'jquery.ui'
'js/models/uploads', 'js/views/uploads', 'js/views/license', 'js/models/license',
'common/js/components/views/feedback_notification', 'jquery.timepicker', 'date', 'gettext',
'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, LearningInfoView, InstructorInfoView, StringUtils) {
- var DetailsView = ValidatingView.extend({
- // Model class is CMS.Models.Settings.CourseDetails
- events: {
- 'input input': 'updateModel',
- 'input textarea': 'updateModel',
- // Leaving change in as fallback for older browsers
- 'change input': 'updateModel',
- 'change textarea': 'updateModel',
- 'change select': 'updateModel',
- 'click .remove-course-introduction-video': 'removeVideo',
- 'focus #course-overview': 'codeMirrorize',
- 'focus #course-about-sidebar-html': 'codeMirrorize',
- 'mouseover .timezone': 'updateTime',
- // 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 .add-course-learning-info': 'addLearningFields',
- 'click .add-course-instructor-info': 'addInstructorFields'
- },
+ function(ValidatingView, CodeMirror, _, $, ui, DateUtils, FileUploadModel,
+ FileUploadDialog, LicenseView, LicenseModel, NotificationView,
+ timepicker, date, gettext, LearningInfoView, InstructorInfoView, StringUtils) {
+ var DetailsView = ValidatingView.extend({
+ // Model class is CMS.Models.Settings.CourseDetails
+ events: {
+ 'input input': 'updateModel',
+ 'input textarea': 'updateModel',
+ // Leaving change in as fallback for older browsers
+ 'change input': 'updateModel',
+ 'change textarea': 'updateModel',
+ 'change select': 'updateModel',
+ 'click .remove-course-introduction-video': 'removeVideo',
+ 'focus #course-overview': 'codeMirrorize',
+ 'focus #course-about-sidebar-html': 'codeMirrorize',
+ 'mouseover .timezone': 'updateTime',
+ // 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 .add-course-learning-info': 'addLearningFields',
+ 'click .add-course-instructor-info': 'addInstructorFields'
+ },
- initialize: function (options) {
- options = options || {};
- // fill in fields
- this.$el.find('#course-language').val(this.model.get('language'));
- this.$el.find('#course-organization').val(this.model.get('org'));
- this.$el.find('#course-number').val(this.model.get('course_id'));
- this.$el.find('#course-name').val(this.model.get('run'));
- this.$el.find('.set-date').datepicker({ dateFormat: 'm/d/yy' });
- this.$el.find("#certificates-display-behavior").val(this.model.get("certificates_display_behavior"));
- this.updateCertificatesDisplayBehavior();
+ initialize: function(options) {
+ options = options || {};
+ // fill in fields
+ this.$el.find('#course-language').val(this.model.get('language'));
+ this.$el.find('#course-organization').val(this.model.get('org'));
+ this.$el.find('#course-number').val(this.model.get('course_id'));
+ this.$el.find('#course-name').val(this.model.get('run'));
+ this.$el.find('.set-date').datepicker({dateFormat: 'm/d/yy'});
- // Avoid showing broken image on mistyped/nonexistent image
- this.$el.find('img').error(function () {
- $(this).hide();
- });
- this.$el.find('img').load(function () {
- $(this).show();
- });
+ // Avoid showing broken image on mistyped/nonexistent image
+ this.$el.find('img').error(function() {
+ $(this).hide();
+ });
+ this.$el.find('img').load(function() {
+ $(this).show();
+ });
- this.listenTo(this.model, 'invalid', this.handleValidationError);
- this.listenTo(this.model, 'change', this.showNotificationBar);
- this.selectorToField = _.invert(this.fieldToSelectorMap);
- // handle license separately, to avoid reimplementing view logic
- this.licenseModel = new LicenseModel({ asString: this.model.get('license') });
- this.licenseView = new LicenseView({
- model: this.licenseModel,
- el: this.$('#course-license-selector').get(),
- showPreview: true
- });
- this.listenTo(this.licenseModel, 'change', this.handleLicenseChange);
+ this.listenTo(this.model, 'invalid', this.handleValidationError);
+ this.listenTo(this.model, 'change', this.showNotificationBar);
+ this.selectorToField = _.invert(this.fieldToSelectorMap);
+ // handle license separately, to avoid reimplementing view logic
+ this.licenseModel = new LicenseModel({asString: this.model.get('license')});
+ this.licenseView = new LicenseView({
+ model: this.licenseModel,
+ el: this.$('#course-license-selector').get(),
+ showPreview: true
+ });
+ this.listenTo(this.licenseModel, 'change', this.handleLicenseChange);
- if (options.showMinGradeWarning || false) {
- new NotificationView.Warning({
- title: gettext('Course Credit Requirements'),
- message: gettext('The minimum grade for course credit is not set.'),
- closeIcon: true
- }).show();
- }
+ if (options.showMinGradeWarning || false) {
+ new NotificationView.Warning({
+ title: gettext('Course Credit Requirements'),
+ message: gettext('The minimum grade for course credit is not set.'),
+ closeIcon: true
+ }).show();
+ }
- this.learning_info_view = new LearningInfoView({
- el: $('.course-settings-learning-fields'),
- model: this.model
- });
+ 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
- });
- },
+ this.instructor_info_view = new InstructorInfoView({
+ el: $('.course-instructor-details-fields'),
+ model: this.model
+ });
+ },
- render: function () {
- // Clear any image preview timeouts set in this.updateImagePreview
- clearTimeout(this.imageTimer);
+ render: function() {
+ // Clear any image preview timeouts set in this.updateImagePreview
+ clearTimeout(this.imageTimer);
- DateUtils.setupDatePicker('start_date', this);
- DateUtils.setupDatePicker('end_date', this);
- DateUtils.setupDatePicker('certificate_available_date', this);
- DateUtils.setupDatePicker('enrollment_start', this);
- DateUtils.setupDatePicker('enrollment_end', this);
- DateUtils.setupDatePicker('upgrade_deadline', this);
+ DateUtils.setupDatePicker('start_date', this);
+ DateUtils.setupDatePicker('end_date', this);
+ DateUtils.setupDatePicker('certificate_available_date', this);
+ DateUtils.setupDatePicker('enrollment_start', this);
+ DateUtils.setupDatePicker('enrollment_end', this);
+ DateUtils.setupDatePicker('upgrade_deadline', this);
- this.$el.find('#' + this.fieldToSelectorMap.overview).val(this.model.get('overview'));
- this.codeMirrorize(null, $('#course-overview')[0]);
+ this.$el.find('#' + this.fieldToSelectorMap.overview).val(this.model.get('overview'));
+ this.codeMirrorize(null, $('#course-overview')[0]);
- if (this.model.get('title') !== '') {
- this.$el.find('#' + this.fieldToSelectorMap.title).val(this.model.get('title'));
- } else {
- var displayName = this.$el.find('#' + this.fieldToSelectorMap.title).attr('data-display-name');
- this.$el.find('#' + this.fieldToSelectorMap.title).val(displayName);
- }
- this.$el.find('#' + this.fieldToSelectorMap.subtitle).val(this.model.get('subtitle'));
- this.$el.find('#' + this.fieldToSelectorMap.duration).val(this.model.get('duration'));
- this.$el.find('#' + this.fieldToSelectorMap.description).val(this.model.get('description'));
+ if (this.model.get('title') !== '') {
+ this.$el.find('#' + this.fieldToSelectorMap.title).val(this.model.get('title'));
+ } else {
+ var displayName = this.$el.find('#' + this.fieldToSelectorMap.title).attr('data-display-name');
+ this.$el.find('#' + this.fieldToSelectorMap.title).val(displayName);
+ }
+ this.$el.find('#' + this.fieldToSelectorMap.subtitle).val(this.model.get('subtitle'));
+ this.$el.find('#' + this.fieldToSelectorMap.duration).val(this.model.get('duration'));
+ this.$el.find('#' + this.fieldToSelectorMap.description).val(this.model.get('description'));
- this.$el.find('#' + this.fieldToSelectorMap.short_description).val(this.model.get('short_description'));
- this.$el.find('#' + this.fieldToSelectorMap.about_sidebar_html).val(
- this.model.get('about_sidebar_html')
- );
- this.codeMirrorize(null, $('#course-about-sidebar-html')[0]);
+ this.$el.find('#' + this.fieldToSelectorMap.short_description).val(this.model.get('short_description'));
+ this.$el.find('#' + this.fieldToSelectorMap.about_sidebar_html).val(
+ this.model.get('about_sidebar_html')
+ );
+ this.codeMirrorize(null, $('#course-about-sidebar-html')[0]);
- this.$el.find('.current-course-introduction-video iframe').attr('src', this.model.videosourceSample());
- this.$el.find('#' + this.fieldToSelectorMap.intro_video).val(this.model.get('intro_video') || '');
- if (this.model.has('intro_video')) {
- this.$el.find('.remove-course-introduction-video').show();
- } else this.$el.find('.remove-course-introduction-video').hide();
+ this.$el.find('.current-course-introduction-video iframe').attr('src', this.model.videosourceSample());
+ this.$el.find('#' + this.fieldToSelectorMap.intro_video).val(this.model.get('intro_video') || '');
+ if (this.model.has('intro_video')) {
+ this.$el.find('.remove-course-introduction-video').show();
+ } else this.$el.find('.remove-course-introduction-video').hide();
- this.$el.find('#' + this.fieldToSelectorMap.effort).val(this.model.get('effort'));
- this.$el.find("#" + this.fieldToSelectorMap.certificates_display_behavior).val(this.model.get('certificates_display_behavior'));
- this.updateCertificatesDisplayBehavior();
+ this.$el.find('#' + this.fieldToSelectorMap.effort).val(this.model.get('effort'));
- var courseImageURL = this.model.get('course_image_asset_path');
- this.$el.find('#course-image-url').val(courseImageURL);
- this.$el.find('#course-image').attr('src', courseImageURL);
+ var courseImageURL = this.model.get('course_image_asset_path');
+ this.$el.find('#course-image-url').val(courseImageURL);
+ this.$el.find('#course-image').attr('src', courseImageURL);
- var bannerImageURL = this.model.get('banner_image_asset_path');
- this.$el.find('#banner-image-url').val(bannerImageURL);
- this.$el.find('#banner-image').attr('src', bannerImageURL);
+ var bannerImageURL = this.model.get('banner_image_asset_path');
+ this.$el.find('#banner-image-url').val(bannerImageURL);
+ this.$el.find('#banner-image').attr('src', bannerImageURL);
- var videoThumbnailImageURL = this.model.get('video_thumbnail_image_asset_path');
- this.$el.find('#video-thumbnail-image-url').val(videoThumbnailImageURL);
- this.$el.find('#video-thumbnail-image').attr('src', videoThumbnailImageURL);
+ var videoThumbnailImageURL = this.model.get('video_thumbnail_image_asset_path');
+ this.$el.find('#video-thumbnail-image-url').val(videoThumbnailImageURL);
+ this.$el.find('#video-thumbnail-image').attr('src', videoThumbnailImageURL);
- var pre_requisite_courses = this.model.get('pre_requisite_courses');
- pre_requisite_courses = pre_requisite_courses.length > 0 ? pre_requisite_courses : '';
- this.$el.find('#' + this.fieldToSelectorMap.pre_requisite_courses).val(pre_requisite_courses);
+ var pre_requisite_courses = this.model.get('pre_requisite_courses');
+ pre_requisite_courses = pre_requisite_courses.length > 0 ? pre_requisite_courses : '';
+ this.$el.find('#' + this.fieldToSelectorMap.pre_requisite_courses).val(pre_requisite_courses);
- if (this.model.get('entrance_exam_enabled') == 'true') {
- this.$('#' + this.fieldToSelectorMap.entrance_exam_enabled).attr('checked', this.model.get('entrance_exam_enabled'));
- this.$('.div-grade-requirements').show();
- } else {
- this.$('#' + this.fieldToSelectorMap.entrance_exam_enabled).removeAttr('checked');
- this.$('.div-grade-requirements').hide();
- }
- this.$('#' + this.fieldToSelectorMap.entrance_exam_minimum_score_pct).val(this.model.get('entrance_exam_minimum_score_pct'));
+ if (this.model.get('entrance_exam_enabled') == 'true') {
+ this.$('#' + this.fieldToSelectorMap.entrance_exam_enabled).attr('checked', this.model.get('entrance_exam_enabled'));
+ this.$('.div-grade-requirements').show();
+ } else {
+ this.$('#' + this.fieldToSelectorMap.entrance_exam_enabled).removeAttr('checked');
+ this.$('.div-grade-requirements').hide();
+ }
+ this.$('#' + this.fieldToSelectorMap.entrance_exam_minimum_score_pct).val(this.model.get('entrance_exam_minimum_score_pct'));
- var selfPacedButton = this.$('#course-pace-self-paced'),
- instructorPacedButton = this.$('#course-pace-instructor-paced'),
- paceToggleTip = this.$('#course-pace-toggle-tip');
- (this.model.get('self_paced') ? selfPacedButton : instructorPacedButton).attr('checked', true);
- if (this.model.canTogglePace()) {
- selfPacedButton.removeAttr('disabled');
- instructorPacedButton.removeAttr('disabled');
- paceToggleTip.text('');
- } else {
- selfPacedButton.attr('disabled', true);
- instructorPacedButton.attr('disabled', true);
- paceToggleTip.text(gettext('Course pacing cannot be changed once a course has started.'));
- }
+ var selfPacedButton = this.$('#course-pace-self-paced'),
+ instructorPacedButton = this.$('#course-pace-instructor-paced'),
+ paceToggleTip = this.$('#course-pace-toggle-tip');
+ (this.model.get('self_paced') ? selfPacedButton : instructorPacedButton).attr('checked', true);
+ if (this.model.canTogglePace()) {
+ selfPacedButton.removeAttr('disabled');
+ instructorPacedButton.removeAttr('disabled');
+ paceToggleTip.text('');
+ } else {
+ selfPacedButton.attr('disabled', true);
+ instructorPacedButton.attr('disabled', true);
+ paceToggleTip.text(gettext('Course pacing cannot be changed once a course has started.'));
+ }
- this.licenseView.render();
- this.learning_info_view.render();
- this.instructor_info_view.render();
+ this.licenseView.render();
+ this.learning_info_view.render();
+ this.instructor_info_view.render();
- return this;
- },
- fieldToSelectorMap: {
- language: 'course-language',
- start_date: 'course-start',
- end_date: 'course-end',
- enrollment_start: 'enrollment-start',
- enrollment_end: 'enrollment-end',
- upgrade_deadline: 'upgrade-deadline',
- certificate_available_date: 'certificate-available',
- certificates_display_behavior: 'certificates-display-behavior',
- overview: 'course-overview',
- title: 'course-title',
- subtitle: 'course-subtitle',
- duration: 'course-duration',
- description: 'course-description',
- about_sidebar_html: 'course-about-sidebar-html',
- short_description: 'course-short-description',
- intro_video: 'course-introduction-video',
- effort: 'course-effort',
- course_image_asset_path: 'course-image-url',
- banner_image_asset_path: 'banner-image-url',
- 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',
- 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'
- },
+ return this;
+ },
+ fieldToSelectorMap: {
+ language: 'course-language',
+ start_date: 'course-start',
+ end_date: 'course-end',
+ enrollment_start: 'enrollment-start',
+ enrollment_end: 'enrollment-end',
+ upgrade_deadline: 'upgrade-deadline',
+ certificate_available_date: 'certificate-available',
+ overview: 'course-overview',
+ title: 'course-title',
+ subtitle: 'course-subtitle',
+ duration: 'course-duration',
+ description: 'course-description',
+ about_sidebar_html: 'course-about-sidebar-html',
+ short_description: 'course-short-description',
+ intro_video: 'course-introduction-video',
+ effort: 'course-effort',
+ course_image_asset_path: 'course-image-url',
+ banner_image_asset_path: 'banner-image-url',
+ 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',
+ 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);
- },
+ 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 });
- },
+ 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) {
- var now = new Date(),
- hours = now.getUTCHours(),
- minutes = now.getUTCMinutes(),
- currentTimeText = StringUtils.interpolate(
- gettext('{hours}:{minutes} (current UTC time)'),
- {
- hours: hours,
- minutes: minutes
- }
- );
+ updateTime: function(e) {
+ var now = new Date(),
+ hours = now.getUTCHours(),
+ minutes = now.getUTCMinutes(),
+ currentTimeText = StringUtils.interpolate(
+ gettext('{hours}:{minutes} (current UTC time)'),
+ {
+ hours: hours,
+ minutes: minutes
+ }
+ );
- $(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-instructor-image-' + index:
- instructors = this.model.get('instructor_info').instructors.slice(0);
- instructors[index].image = $(event.currentTarget).val();
- this.model.set('instructor_info', { instructors: instructors });
- this.showNotificationBar();
- this.updateImagePreview(event.currentTarget, '#course-instructor-image-preview-' + index);
- 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':
- this.updateImageField(event, 'video_thumbnail_image_name', '#video-thumbnail-image');
- break;
- case 'entrance-exam-enabled':
- if ($(event.currentTarget).is(':checked')) {
- this.$('.div-grade-requirements').show();
- } else {
- this.$('.div-grade-requirements').hide();
- }
- this.setField(event);
- break;
- case 'entrance-exam-minimum-score-pct':
- // If the val is an empty string then update model with default value.
- if ($(event.currentTarget).val() === '') {
- this.model.set('entrance_exam_minimum_score_pct', this.model.defaults.entrance_exam_minimum_score_pct);
- } else {
- this.setField(event);
- }
- break;
- case 'pre-requisite-course':
- var value = $(event.currentTarget).val();
- value = value == '' ? [] : [value];
- this.model.set('pre_requisite_courses', value);
- break;
- // Don't make the user reload the page to check the Youtube ID.
- // Wait for a second to load the video, avoiding egregious AJAX calls.
- case 'course-introduction-video':
- this.clearValidationErrors();
- var previewsource = this.model.set_videosource($(event.currentTarget).val());
- clearTimeout(this.videoTimer);
- this.videoTimer = setTimeout(_.bind(function () {
- this.$el.find('.current-course-introduction-video iframe').attr('src', previewsource);
- if (this.model.has('intro_video')) {
- this.$el.find('.remove-course-introduction-video').show();
- } else {
- this.$el.find('.remove-course-introduction-video').hide();
- }
- }, this), 1000);
- break;
- case 'course-pace-self-paced':
- // Fallthrough to handle both radio buttons
- case 'course-pace-instructor-paced':
- this.model.set('self_paced', JSON.parse(event.currentTarget.value));
- break;
- case 'certificates-display-behavior':
- this.setField(event);
- this.updateCertificatesDisplayBehavior();
- break;
- case 'course-language':
- case 'course-effort':
- case 'course-title':
- case 'course-subtitle':
- case 'course-duration':
- case 'course-description':
- case 'course-short-description':
- this.setField(event);
- break;
- default: // Everything else is handled by datepickers and CodeMirror.
- break;
- }
- },
- updateImageField: function (event, image_field, selector) {
- this.setField(event);
- var url = $(event.currentTarget).val();
- var image_name = _.last(url.split('/'));
- // If image path is entered directly, we need to strip the asset prefix
- image_name = _.last(image_name.split('block@'));
- this.model.set(image_field, image_name);
- this.updateImagePreview(event.currentTarget, selector);
- },
- updateImagePreview: function (imagePathInputElement, previewSelector) {
- // Wait to set the image src until the user stops typing
- clearTimeout(this.imageTimer);
- this.imageTimer = setTimeout(function () {
- $(previewSelector).attr('src', $(imagePathInputElement).val());
- }, 1000);
- },
- removeVideo: function (event) {
- event.preventDefault();
- if (this.model.has('intro_video')) {
- this.model.set_videosource(null);
- this.$el.find('.current-course-introduction-video iframe').attr('src', '');
- this.$el.find('#' + this.fieldToSelectorMap.intro_video).val('');
- this.$el.find('.remove-course-introduction-video').hide();
- }
- },
- codeMirrors: {},
- codeMirrorize: function (e, forcedTarget) {
- var thisTarget, cachethis, field, cmTextArea;
- if (forcedTarget) {
- thisTarget = forcedTarget;
- thisTarget.id = $(thisTarget).attr('id');
- } else if (e !== null) {
- thisTarget = e.currentTarget;
- } else {
- // e and forcedTarget can be null so don't deference it
- // This is because in cases where we have a marketing site
- // we don't display the codeMirrors for editing the marketing
- // materials, except we do need to show the 'set course image'
- // workflow. So in this case e = forcedTarget = null.
- return;
- }
+ $(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-instructor-image-' + index:
+ instructors = this.model.get('instructor_info').instructors.slice(0);
+ instructors[index].image = $(event.currentTarget).val();
+ this.model.set('instructor_info', {instructors: instructors});
+ this.showNotificationBar();
+ this.updateImagePreview(event.currentTarget, '#course-instructor-image-preview-' + index);
+ 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':
+ this.updateImageField(event, 'video_thumbnail_image_name', '#video-thumbnail-image');
+ break;
+ case 'entrance-exam-enabled':
+ if ($(event.currentTarget).is(':checked')) {
+ this.$('.div-grade-requirements').show();
+ } else {
+ this.$('.div-grade-requirements').hide();
+ }
+ this.setField(event);
+ break;
+ case 'entrance-exam-minimum-score-pct':
+ // If the val is an empty string then update model with default value.
+ if ($(event.currentTarget).val() === '') {
+ this.model.set('entrance_exam_minimum_score_pct', this.model.defaults.entrance_exam_minimum_score_pct);
+ } else {
+ this.setField(event);
+ }
+ break;
+ case 'pre-requisite-course':
+ var value = $(event.currentTarget).val();
+ value = value == '' ? [] : [value];
+ this.model.set('pre_requisite_courses', value);
+ break;
+ // Don't make the user reload the page to check the Youtube ID.
+ // Wait for a second to load the video, avoiding egregious AJAX calls.
+ case 'course-introduction-video':
+ this.clearValidationErrors();
+ var previewsource = this.model.set_videosource($(event.currentTarget).val());
+ clearTimeout(this.videoTimer);
+ this.videoTimer = setTimeout(_.bind(function() {
+ this.$el.find('.current-course-introduction-video iframe').attr('src', previewsource);
+ if (this.model.has('intro_video')) {
+ this.$el.find('.remove-course-introduction-video').show();
+ } else {
+ this.$el.find('.remove-course-introduction-video').hide();
+ }
+ }, this), 1000);
+ break;
+ case 'course-pace-self-paced':
+ // Fallthrough to handle both radio buttons
+ case 'course-pace-instructor-paced':
+ this.model.set('self_paced', JSON.parse(event.currentTarget.value));
+ break;
+ case 'course-language':
+ case 'course-effort':
+ case 'course-title':
+ case 'course-subtitle':
+ case 'course-duration':
+ case 'course-description':
+ case 'course-short-description':
+ this.setField(event);
+ break;
+ default: // Everything else is handled by datepickers and CodeMirror.
+ break;
+ }
+ },
+ updateImageField: function(event, image_field, selector) {
+ this.setField(event);
+ var url = $(event.currentTarget).val();
+ var image_name = _.last(url.split('/'));
+ // If image path is entered directly, we need to strip the asset prefix
+ image_name = _.last(image_name.split('block@'));
+ this.model.set(image_field, image_name);
+ this.updateImagePreview(event.currentTarget, selector);
+ },
+ updateImagePreview: function(imagePathInputElement, previewSelector) {
+ // Wait to set the image src until the user stops typing
+ clearTimeout(this.imageTimer);
+ this.imageTimer = setTimeout(function() {
+ $(previewSelector).attr('src', $(imagePathInputElement).val());
+ }, 1000);
+ },
+ removeVideo: function(event) {
+ event.preventDefault();
+ if (this.model.has('intro_video')) {
+ this.model.set_videosource(null);
+ this.$el.find('.current-course-introduction-video iframe').attr('src', '');
+ this.$el.find('#' + this.fieldToSelectorMap.intro_video).val('');
+ this.$el.find('.remove-course-introduction-video').hide();
+ }
+ },
+ codeMirrors: {},
+ codeMirrorize: function(e, forcedTarget) {
+ var thisTarget, cachethis, field, cmTextArea;
+ if (forcedTarget) {
+ thisTarget = forcedTarget;
+ thisTarget.id = $(thisTarget).attr('id');
+ } else if (e !== null) {
+ thisTarget = e.currentTarget;
+ } else {
+ // e and forcedTarget can be null so don't deference it
+ // This is because in cases where we have a marketing site
+ // we don't display the codeMirrors for editing the marketing
+ // materials, except we do need to show the 'set course image'
+ // workflow. So in this case e = forcedTarget = null.
+ return;
+ }
- if (!this.codeMirrors[thisTarget.id]) {
- cachethis = this;
- field = this.selectorToField[thisTarget.id];
- this.codeMirrors[thisTarget.id] = CodeMirror.fromTextArea(thisTarget, {
- mode: 'text/html', lineNumbers: true, lineWrapping: true
- });
- this.codeMirrors[thisTarget.id].on('change', function (mirror) {
- mirror.save();
- cachethis.clearValidationErrors();
- var newVal = mirror.getValue();
- if (cachethis.model.get(field) != newVal) {
- cachethis.setAndValidate(field, newVal);
- }
- });
- cmTextArea = this.codeMirrors[thisTarget.id].getInputField();
- cmTextArea.setAttribute('id', thisTarget.id + '-cm-textarea');
- }
- },
+ if (!this.codeMirrors[thisTarget.id]) {
+ cachethis = this;
+ field = this.selectorToField[thisTarget.id];
+ this.codeMirrors[thisTarget.id] = CodeMirror.fromTextArea(thisTarget, {
+ mode: 'text/html', lineNumbers: true, lineWrapping: true});
+ this.codeMirrors[thisTarget.id].on('change', function(mirror) {
+ mirror.save();
+ cachethis.clearValidationErrors();
+ var newVal = mirror.getValue();
+ if (cachethis.model.get(field) != newVal) {
+ cachethis.setAndValidate(field, newVal);
+ }
+ });
+ cmTextArea = this.codeMirrors[thisTarget.id].getInputField();
+ cmTextArea.setAttribute('id', thisTarget.id + '-cm-textarea');
+ }
+ },
- updateCertificatesDisplayBehavior: function() {
- /*
- Hides and clears the certificate available date field if a display behavior that doesn't use it is
- chosen. Because we are clearing it, toggling back to "end_with_date" will require re-entering the date
- */
- console.info("IN UPDATER");
- let showDatepicker = this.model.get("certificates_display_behavior") == "end_with_date";
- let datepicker = this.$el.find('#certificate-available-date');
- let certificateAvailableDateField = this.$el.find('#field-certificate-available-date');
+ revertView: function() {
+ // Make sure that the CodeMirror instance has the correct
+ // data from its corresponding textarea
+ var self = this;
+ this.model.fetch({
+ success: function() {
+ self.render();
+ _.each(self.codeMirrors, function(mirror) {
+ var ele = mirror.getTextArea();
+ var field = self.selectorToField[ele.id];
+ mirror.setValue(self.model.get(field));
+ });
+ self.licenseModel.setFromString(self.model.get('license'), {silent: true});
+ self.licenseView.render();
+ },
+ reset: true,
+ silent: true});
+ },
+ setAndValidate: function(attr, value) {
+ // If we call model.set() with {validate: true}, model fields
+ // will not be set if validation fails. This puts the UI and
+ // the model in an inconsistent state, and causes us to not
+ // see the right validation errors the next time validate() is
+ // called on the model. So we set *without* validating, then
+ // call validate ourselves.
+ this.model.set(attr, value);
+ this.model.isValid();
+ },
- if (showDatepicker) {
- console.info("Value is end_with_date");
- datepicker.prop('disabled', false);
- certificateAvailableDateField.removeClass("hidden");
- } else {
- console.info("Value is NOT end_with_date");
- datepicker.prop('disabled', true);
- datepicker.val(null);
- this.clearValidationErrors();
- this.setAndValidate("certificate_available_date", null)
- certificateAvailableDateField.addClass("hidden");
- }
- },
- revertView: function () {
- // Make sure that the CodeMirror instance has the correct
- // data from its corresponding textarea
- var self = this;
- this.model.fetch({
- success: function () {
- self.render();
- _.each(self.codeMirrors, function (mirror) {
- var ele = mirror.getTextArea();
- var field = self.selectorToField[ele.id];
- mirror.setValue(self.model.get(field));
- });
- self.licenseModel.setFromString(self.model.get('license'), { silent: true });
- self.licenseView.render();
- },
- reset: true,
- silent: true
- });
- },
- setAndValidate: function (attr, value) {
- // If we call model.set() with {validate: true}, model fields
- // will not be set if validation fails. This puts the UI and
- // the model in an inconsistent state, and causes us to not
- // see the right validation errors the next time validate() is
- // called on the model. So we set *without* validating, then
- // call validate ourselves.
- this.model.set(attr, value);
- this.model.isValid();
- },
+ showNotificationBar: function() {
+ // We always call showNotificationBar with the same args, just
+ // delegate to superclass
+ ValidatingView.prototype.showNotificationBar.call(this,
+ this.save_message,
+ _.bind(this.saveView, this),
+ _.bind(this.revertView, this));
+ },
- showNotificationBar: function () {
- // We always call showNotificationBar with the same args, just
- // delegate to superclass
- ValidatingView.prototype.showNotificationBar.call(this,
- this.save_message,
- _.bind(this.saveView, this),
- _.bind(this.revertView, this));
- },
+ uploadImage: function(event) {
+ event.preventDefault();
+ var title = '',
+ selector = '',
+ image_key = '',
+ image_path_key = '';
+ switch (event.currentTarget.id) {
+ case 'upload-course-image':
+ title = gettext('Upload your course image.');
+ selector = '#course-image';
+ image_key = 'course_image_name';
+ image_path_key = 'course_image_asset_path';
+ break;
+ case 'upload-banner-image':
+ title = gettext('Upload your banner image.');
+ selector = '#banner-image';
+ image_key = 'banner_image_name';
+ image_path_key = 'banner_image_asset_path';
+ break;
+ case 'upload-video-thumbnail-image':
+ title = gettext('Upload your video thumbnail image.');
+ selector = '#video-thumbnail-image';
+ image_key = 'video_thumbnail_image_name';
+ image_path_key = 'video_thumbnail_image_asset_path';
+ break;
+ }
- uploadImage: function (event) {
- event.preventDefault();
- var title = '',
- selector = '',
- image_key = '',
- image_path_key = '';
- switch (event.currentTarget.id) {
- case 'upload-course-image':
- title = gettext('Upload your course image.');
- selector = '#course-image';
- image_key = 'course_image_name';
- image_path_key = 'course_image_asset_path';
- break;
- case 'upload-banner-image':
- title = gettext('Upload your banner image.');
- selector = '#banner-image';
- image_key = 'banner_image_name';
- image_path_key = 'banner_image_asset_path';
- break;
- case 'upload-video-thumbnail-image':
- title = gettext('Upload your video thumbnail image.');
- selector = '#video-thumbnail-image';
- image_key = 'video_thumbnail_image_name';
- image_path_key = 'video_thumbnail_image_asset_path';
- break;
- }
+ var upload = new FileUploadModel({
+ title: title,
+ 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) {
+ var options = {};
+ options[image_key] = response.asset.display_name;
+ options[image_path_key] = response.asset.url;
+ self.model.set(options);
+ self.render();
+ $(selector).attr('src', self.model.get(image_path_key));
+ }
+ });
+ modal.show();
+ },
- var upload = new FileUploadModel({
- title: title,
- 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) {
- var options = {};
- options[image_key] = response.asset.display_name;
- options[image_path_key] = response.asset.url;
- self.model.set(options);
- self.render();
- $(selector).attr('src', self.model.get(image_path_key));
- }
- });
- modal.show();
- },
+ handleLicenseChange: function() {
+ this.showNotificationBar();
+ this.model.set('license', this.licenseModel.toString());
+ }
+ });
- handleLicenseChange: function () {
- this.showNotificationBar();
- this.model.set('license', this.licenseModel.toString());
- }
- });
-
- return DetailsView;
- }); // end define()
+ return DetailsView;
+ }); // end define()
diff --git a/cms/static/sass/views/_settings.scss b/cms/static/sass/views/_settings.scss
index 78e9837fd9..de7c156cd9 100644
--- a/cms/static/sass/views/_settings.scss
+++ b/cms/static/sass/views/_settings.scss
@@ -157,29 +157,6 @@
color: $red;
}
- .collapsible {
- .collapsible-trigger{
- color: theme-color("primary");
- &:hover{
- cursor: pointer;
- }
- }
-
- .collapsible-content{
- font-size: 12px;
- padding-top: ($baseline/2);
- .collapsible-description-heading {
- font-weight: bold;
- margin-top: $baseline;
- }
-
- &.collapsed{
- height: 0;
- overflow: hidden;
- }
- }
- }
-
// buttons
.remove-item {
@include white-button;
@@ -245,14 +222,10 @@
}
}
- &.hidden {
- display: none;
- }
-
label,
input,
textarea {
- display: inline-block;
+ display: block;
}
label {
@@ -269,8 +242,7 @@
}
input,
- textarea,
- select {
+ textarea {
@extend %t-copy-base;
@include placeholder($gray-l4);
@@ -314,10 +286,6 @@
display: inline-block;
}
}
-
- .datepicker-icon {
- margin-left: -($baseline * 1.5);
- }
}
.field-group {
@@ -510,7 +478,7 @@
.field {
@include float(left);
- width: flex-grid(4, 9);
+ width: flex-grid(3, 9);
margin-bottom: ($baseline/4);
margin-right: flex-gutter();
}
diff --git a/cms/templates/settings.html b/cms/templates/settings.html
index a4762c5d3f..22832c4adc 100644
--- a/cms/templates/settings.html
+++ b/cms/templates/settings.html
@@ -223,7 +223,6 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url | n, js_escaped_string}'
-
${_("First day the course begins")}
@@ -238,7 +237,6 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url | n, js_escaped_string}'
-
${_("Last day your course is active")}
@@ -253,45 +251,10 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url | n, js_escaped_string}'
% if can_show_certificate_available_date_field(context_course):
-
-
-
- ${_("Certificates are awarded at the end of a course run")}
-
-
-
-
-
-
- Read more about this setting
-
-
-
-
In all configurations of this setting, certificates are generated for learners as soon as they achieve the passing threshold in the course (which can occur before a final assignment based on course design)
-
-
Immediately upon passing
-
Learners can access their certificate as soon as they achieve a passing grade above the course grade threshold. Note: learners can achieve a passing grade before encountering all assignments in some course configurations.
-
-
-
On course end date
-
Learners with passing grades can access their certificate once the end date of the course has elapsed.
-
-
-
A date after the course end date
-
Learners with passing grades can access their certificate after the date that you set has elapsed.
-
-
-
-
-
-
+
-
+ ${_("By default, 48 hours after course end date")}
@@ -302,7 +265,6 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url | n, js_escaped_string}'
-
${_("First day students can enroll")}
@@ -320,7 +282,6 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url | n, js_escaped_string}'
-
${_("Last day students can enroll.")}
% if not enrollment_end_editable:
diff --git a/common/djangoapps/student/helpers.py b/common/djangoapps/student/helpers.py
index cfcd08e6d3..a632dc4696 100644
--- a/common/djangoapps/student/helpers.py
+++ b/common/djangoapps/student/helpers.py
@@ -49,7 +49,6 @@ from lms.djangoapps.verify_student.utils import is_verification_expiring_soon, v
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.theming.helpers import get_themes
from openedx.core.djangoapps.user_authn.utils import is_safe_login_or_logout_redirect
-from xmodule.data import CertificatesDisplayBehaviors
# Enumeration of per-course verification statuses
# we display on the student dashboard.
@@ -512,17 +511,14 @@ def _cert_info(user, course_overview, cert_status):
is_hidden_status = status in ('processing', 'generating', 'notpassing', 'auditing')
if (
- not certificates_viewable_for_course(course_overview)
- and CertificateStatuses.is_passing_status(status)
- and course_overview.certificates_display_behavior in (
- CertificatesDisplayBehaviors.END_WITH_DATE,
- CertificatesDisplayBehaviors.END
- )
+ not certificates_viewable_for_course(course_overview) and
+ CertificateStatuses.is_passing_status(status) and
+ course_overview.certificate_available_date
):
status = certificate_earned_but_not_available_status
if (
- course_overview.certificates_display_behavior == CertificatesDisplayBehaviors.EARLY_NO_INFO and
+ course_overview.certificates_display_behavior == 'early_no_info' and
is_hidden_status
):
return default_info
diff --git a/common/djangoapps/student/tests/test_certificates.py b/common/djangoapps/student/tests/test_certificates.py
index f83d76dff1..6f01490386 100644
--- a/common/djangoapps/student/tests/test_certificates.py
+++ b/common/djangoapps/student/tests/test_certificates.py
@@ -22,7 +22,6 @@ from lms.djangoapps.certificates.tests.factories import (
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
-from xmodule.data import CertificatesDisplayBehaviors
# pylint: disable=no-member
@@ -41,7 +40,7 @@ class CertificateDisplayTestBase(SharedModuleStoreTestCase):
def setUpClass(cls):
super().setUpClass()
cls.course = CourseFactory()
- cls.course.certificates_display_behavior = CertificatesDisplayBehaviors.EARLY_NO_INFO
+ cls.course.certificates_display_behavior = "early_with_info"
with cls.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, cls.course.id):
cls.store.update_item(cls.course, cls.USERNAME)
@@ -117,54 +116,40 @@ class CertificateDashboardMessageDisplayTest(CertificateDisplayTestBase):
@classmethod
def setUpClass(cls):
super().setUpClass()
- cls.course.certificates_display_behavior = CertificatesDisplayBehaviors.END_WITH_DATE
+ cls.course.certificates_display_behavior = "end"
cls.course.save()
cls.store.update_item(cls.course, cls.USERNAME)
- def _check_message(self, visible_date): # lint-amnesty, pylint: disable=missing-function-docstring
+ def _check_message(self, certificate_available_date): # lint-amnesty, pylint: disable=missing-function-docstring
response = self.client.get(reverse('dashboard'))
test_message = 'Your grade and certificate will be ready after'
-
- is_past = visible_date < datetime.datetime.now(UTC)
-
- if is_past:
+ if certificate_available_date is None:
self.assertNotContains(response, test_message)
self.assertNotContains(response, "View Test_Certificate")
- self._check_can_download_certificate()
-
- else:
+ elif datetime.datetime.now(UTC) < certificate_available_date:
self.assertContains(response, test_message)
self.assertNotContains(response, "View Test_Certificate")
+ else:
+ self._check_can_download_certificate()
- @ddt.data(
- (CertificatesDisplayBehaviors.END, True),
- (CertificatesDisplayBehaviors.END, False),
- (CertificatesDisplayBehaviors.END_WITH_DATE, True),
- (CertificatesDisplayBehaviors.END_WITH_DATE, False)
- )
- @ddt.unpack
- def test_certificate_available_date(self, certificates_display_behavior, past_date):
+ @ddt.data(True, False, None)
+ def test_certificate_available_date(self, past_certificate_available_date):
cert = self._create_certificate('verified')
cert.status = CertificateStatuses.downloadable
cert.save()
- self.course.certificates_display_behavior = certificates_display_behavior
-
- if certificates_display_behavior == CertificatesDisplayBehaviors.END:
- if past_date:
- self.course.end = PAST_DATE
- else:
- self.course.end = FUTURE_DATE
- if certificates_display_behavior == CertificatesDisplayBehaviors.END_WITH_DATE:
- if past_date:
- self.course.certificate_available_date = PAST_DATE
- else:
- self.course.certificate_available_date = FUTURE_DATE
+ if past_certificate_available_date is None:
+ certificate_available_date = None
+ elif past_certificate_available_date:
+ certificate_available_date = PAST_DATE
+ elif not past_certificate_available_date:
+ certificate_available_date = FUTURE_DATE
+ self.course.certificate_available_date = certificate_available_date
self.course.save()
self.store.update_item(self.course, self.USERNAME)
- self._check_message(PAST_DATE if past_date else FUTURE_DATE)
+ self._check_message(certificate_available_date)
@ddt.ddt
diff --git a/common/djangoapps/student/tests/test_views.py b/common/djangoapps/student/tests/test_views.py
index 7fe7eef2f2..3ee0dbd7c7 100644
--- a/common/djangoapps/student/tests/test_views.py
+++ b/common/djangoapps/student/tests/test_views.py
@@ -40,7 +40,6 @@ from openedx.core.djangoapps.content.course_overviews.tests.factories import Cou
from openedx.core.djangoapps.site_configuration.tests.test_util import with_site_configuration_context
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
from openedx.features.course_experience.tests.views.helpers import add_course_mode
-from xmodule.data import CertificatesDisplayBehaviors
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
@@ -228,7 +227,6 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin,
id=course_key,
end_date=THREE_YEARS_AGO,
certificate_available_date=TOMORROW,
- certificates_display_behavior=CertificatesDisplayBehaviors.END_WITH_DATE,
lowest_passing_grade=0.3
)
CourseEnrollmentFactory(course_id=course.id, user=self.user)
@@ -244,7 +242,6 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin,
id=course_key,
end_date=TOMORROW,
certificate_available_date=TOMORROW,
- certificates_display_behavior=CertificatesDisplayBehaviors.END_WITH_DATE,
lowest_passing_grade=0.3
)
CourseEnrollmentFactory(course_id=course.id, user=self.user)
@@ -260,7 +257,6 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin,
id=course_key,
end_date=ONE_WEEK_AGO,
certificate_available_date=now(),
- certificates_display_behavior=CertificatesDisplayBehaviors.END_WITH_DATE,
lowest_passing_grade=0.3
)
CourseEnrollmentFactory(course_id=course.id, user=self.user)
diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py
index 341eecf375..c1716409b9 100644
--- a/common/djangoapps/student/tests/tests.py
+++ b/common/djangoapps/student/tests/tests.py
@@ -49,8 +49,6 @@ from openedx.core.djangoapps.site_configuration.tests.mixins import SiteMixin
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms
from xmodule.modulestore.tests.django_utils import ModuleStoreEnum, ModuleStoreTestCase, SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls
-from xmodule.data import CertificatesDisplayBehaviors
-
log = logging.getLogger(__name__)
@@ -77,8 +75,7 @@ class CourseEndingTest(ModuleStoreTestCase):
survey_url = "http://a_survey.com"
course = CourseOverviewFactory.create(
end_of_course_survey_url=survey_url,
- certificates_display_behavior=CertificatesDisplayBehaviors.END,
- end=datetime.now(pytz.UTC) - timedelta(days=2)
+ certificates_display_behavior='end',
)
cert = GeneratedCertificateFactory.create(
user=user,
@@ -141,7 +138,7 @@ class CourseEndingTest(ModuleStoreTestCase):
'can_unenroll': True}
# test when the display is unavailable or notpassing, we get the correct results out
- course2.certificates_display_behavior = CertificatesDisplayBehaviors.EARLY_NO_INFO
+ course2.certificates_display_behavior = 'early_no_info'
cert_status = {'status': 'unavailable', 'mode': 'honor', 'uuid': None}
assert _cert_info(user, course2, cert_status) == {'status': 'processing', 'show_survey_button': False,
'can_unenroll': True}
@@ -176,8 +173,7 @@ class CourseEndingTest(ModuleStoreTestCase):
survey_url = "http://a_survey.com"
course = CourseOverviewFactory.create(
end_of_course_survey_url=survey_url,
- certificates_display_behavior=CertificatesDisplayBehaviors.END,
- end=datetime.now(pytz.UTC) - timedelta(days=2),
+ certificates_display_behavior='end',
)
if cert_grade is not None:
@@ -202,8 +198,7 @@ class CourseEndingTest(ModuleStoreTestCase):
survey_url = "http://a_survey.com"
course = CourseOverviewFactory.create(
end_of_course_survey_url=survey_url,
- certificates_display_behavior=CertificatesDisplayBehaviors.END,
- end=datetime.now(pytz.UTC) - timedelta(days=2),
+ certificates_display_behavior='end',
)
cert_status = {'status': 'generating', 'mode': 'honor', 'uuid': None}
diff --git a/common/lib/xmodule/xmodule/course_metadata_utils.py b/common/lib/xmodule/xmodule/course_metadata_utils.py
index ec1913a002..b724f7c229 100644
--- a/common/lib/xmodule/xmodule/course_metadata_utils.py
+++ b/common/lib/xmodule/xmodule/course_metadata_utils.py
@@ -14,8 +14,6 @@ from math import exp
import dateutil.parser
from pytz import utc
-from xmodule.data import CertificatesDisplayBehaviors
-
DEFAULT_START_DATE = datetime(2030, 1, 1, tzinfo=utc)
"""
@@ -158,18 +156,15 @@ def may_certify_for_course(
self_paced (bool): Whether the course is self-paced.
"""
show_early = (
- certificates_display_behavior == CertificatesDisplayBehaviors.EARLY_NO_INFO
+ certificates_display_behavior in ('early_with_info', 'early_no_info')
or certificates_show_before_end
)
past_available_date = (
- certificates_display_behavior == CertificatesDisplayBehaviors.END_WITH_DATE
- and certificate_available_date
+ certificate_available_date
and certificate_available_date < datetime.now(utc)
)
- ended_without_available_date = (
- certificates_display_behavior == CertificatesDisplayBehaviors.END
- and has_ended
- )
+ ended_without_available_date = (certificate_available_date is None) and has_ended
+
return any((self_paced, show_early, past_available_date, ended_without_available_date))
diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py
index 1920c05d78..1f934df02a 100644
--- a/common/lib/xmodule/xmodule/course_module.py
+++ b/common/lib/xmodule/xmodule/course_module.py
@@ -5,7 +5,7 @@ Django module container for classes and operations related to the "Course Module
import json
import logging
-from datetime import datetime
+from datetime import datetime, timedelta
from io import BytesIO
import dateutil.parser
@@ -22,7 +22,6 @@ from openedx.core.lib.license import LicenseMixin
from openedx.core.lib.teams_config import TeamsConfig # lint-amnesty, pylint: disable=unused-import
from xmodule import course_metadata_utils
from xmodule.course_metadata_utils import DEFAULT_GRADING_POLICY, DEFAULT_START_DATE
-from xmodule.data import CertificatesDisplayBehaviors
from xmodule.graders import grader_from_conf
from xmodule.seq_module import SequenceBlock
from xmodule.tabs import CourseTabList, InvalidTabsException
@@ -556,15 +555,15 @@ class CourseFields: # lint-amnesty, pylint: disable=missing-class-docstring
certificates_display_behavior = String(
display_name=_("Certificates Display Behavior"),
help=_(
- "Enter end, end_with_date, or early_no_info. After certificate generation, students who passed see a "
+ "Enter end, early_with_info, or early_no_info. After certificate generation, students who passed see a "
"link to their certificates on the dashboard and students who did not pass see information about the "
"grading configuration. The default is end, which displays this certificate information to all students "
- "after the course end date. To display the certificate information to all students at a date after the "
- "course end date, use end_with_date and add a certificate_available_date. To display only the links to "
- "passing students as soon as certificates are generated, enter early_no_info."
+ "after the course end date. To display this certificate information to all students as soon as "
+ "certificates are generated, enter early_with_info. To display only the links to passing students as "
+ "soon as certificates are generated, enter early_no_info."
),
scope=Scope.settings,
- default=CertificatesDisplayBehaviors.END,
+ default="end"
)
course_image = String(
display_name=_("Course About Page Image"),
@@ -1062,6 +1061,8 @@ class CourseBlock(
except InvalidTabsException as err:
raise type(err)(f'{str(err)} For course: {str(self.id)}') # lint-amnesty, pylint: disable=line-too-long
+ self.set_default_certificate_available_date()
+
def set_grading_policy(self, course_policy):
"""
The JSON object can have the keys GRADER and GRADE_CUTOFFS. If either is
@@ -1087,6 +1088,10 @@ class CourseBlock(
self.raw_grader = grading_policy['GRADER'] # used for cms access
self.grade_cutoffs = grading_policy['GRADE_CUTOFFS']
+ def set_default_certificate_available_date(self):
+ if (not self.certificate_available_date) and self.end:
+ self.certificate_available_date = self.end + timedelta(days=2)
+
@classmethod
def read_grading_policy(cls, paths, system):
"""Load a grading policy from the specified paths, in order, if it exists."""
diff --git a/common/lib/xmodule/xmodule/data.py b/common/lib/xmodule/xmodule/data.py
deleted file mode 100644
index b7622547d7..0000000000
--- a/common/lib/xmodule/xmodule/data.py
+++ /dev/null
@@ -1,25 +0,0 @@
-"""
-Public data structures for this app.
-
-See OEP-49 for details
-"""
-from enum import Enum
-
-
-class CertificatesDisplayBehaviors(str, Enum):
- """
- Options for the certificates_display_behavior field of a course
-
- end: Certificates are available at the end of the course
- end_with_date: Certificates are available after the certificate_available_date (post course end)
- early_no_info: Certificates are available immediately after earning them.
-
- Only in affect for instructor based courses.
- """
- END = "end"
- END_WITH_DATE = "end_with_date"
- EARLY_NO_INFO = "early_no_info"
-
- @classmethod
- def includes_value(cls, value):
- return value in set(item.value for item in cls)
diff --git a/common/lib/xmodule/xmodule/tests/test_course_metadata_utils.py b/common/lib/xmodule/xmodule/tests/test_course_metadata_utils.py
index 841047e9ee..57ff45492d 100644
--- a/common/lib/xmodule/xmodule/tests/test_course_metadata_utils.py
+++ b/common/lib/xmodule/xmodule/tests/test_course_metadata_utils.py
@@ -23,7 +23,6 @@ from xmodule.course_metadata_utils import (
may_certify_for_course,
number_for_course_location
)
-from xmodule.data import CertificatesDisplayBehaviors
from xmodule.modulestore.tests.utils import (
MixedModulestoreBuilder,
MongoModulestoreBuilder,
@@ -164,28 +163,16 @@ class CourseMetadataUtilsTestCase(TestCase):
TestScenario((DEFAULT_START_DATE, None), True),
]),
FunctionTest(may_certify_for_course, [
- # Test certificates_show_before_end
- TestScenario((CertificatesDisplayBehaviors.EARLY_NO_INFO, True, False, test_datetime, False), True),
- TestScenario((CertificatesDisplayBehaviors.END, True, False, test_datetime, False), True),
- TestScenario((CertificatesDisplayBehaviors.END_WITH_DATE, True, False, _NEXT_WEEK, False), True),
-
- # Test that EARLY_NO_INFO
- TestScenario((CertificatesDisplayBehaviors.EARLY_NO_INFO, True, True, test_datetime, False), True),
- TestScenario((CertificatesDisplayBehaviors.EARLY_NO_INFO, False, False, test_datetime, False), True),
-
- # Test END_WITH_DATE
- TestScenario((CertificatesDisplayBehaviors.END_WITH_DATE, False, False, test_datetime, False), True),
- TestScenario((CertificatesDisplayBehaviors.END_WITH_DATE, False, False, _LAST_WEEK, False), True),
- TestScenario((CertificatesDisplayBehaviors.END_WITH_DATE, False, False, _NEXT_WEEK, False), False),
- TestScenario((CertificatesDisplayBehaviors.END_WITH_DATE, False, False, None, False), False),
-
- # Test END
- TestScenario((CertificatesDisplayBehaviors.END, False, False, test_datetime, False), False),
- TestScenario((CertificatesDisplayBehaviors.END, False, True, test_datetime, False), True),
-
- # Test self_paced
- TestScenario((CertificatesDisplayBehaviors.END, False, False, test_datetime, False), False),
- TestScenario((CertificatesDisplayBehaviors.END, False, False, test_datetime, True), True),
+ TestScenario(('early_with_info', True, True, test_datetime, False), True),
+ TestScenario(('early_no_info', False, False, test_datetime, False), True),
+ TestScenario(('end', True, False, test_datetime, False), True),
+ TestScenario(('end', False, True, test_datetime, False), True),
+ TestScenario(('end', False, False, _NEXT_WEEK, False), False),
+ TestScenario(('end', False, False, _LAST_WEEK, False), True),
+ TestScenario(('end', False, False, None, False), False),
+ TestScenario(('early_with_info', False, False, None, False), True),
+ TestScenario(('end', False, False, _NEXT_WEEK, False), False),
+ TestScenario(('end', False, False, _NEXT_WEEK, True), True),
]),
]
diff --git a/common/lib/xmodule/xmodule/tests/test_course_module.py b/common/lib/xmodule/xmodule/tests/test_course_module.py
index 071d28074a..dbe49300aa 100644
--- a/common/lib/xmodule/xmodule/tests/test_course_module.py
+++ b/common/lib/xmodule/xmodule/tests/test_course_module.py
@@ -18,7 +18,6 @@ from xblock.runtime import DictKeyValueStore, KvsFieldData
from openedx.core.lib.teams_config import TeamsConfig, DEFAULT_COURSE_RUN_MAX_TEAM_SIZE
import xmodule.course_module
-from xmodule.data import CertificatesDisplayBehaviors
from xmodule.modulestore.xml import ImportSystem, XMLModuleStore
from xmodule.modulestore.exceptions import InvalidProctoringProvider
@@ -106,41 +105,36 @@ class HasEndedMayCertifyTestCase(unittest.TestCase):
super().setUp()
system = DummySystem(load_error_modules=True) # lint-amnesty, pylint: disable=unused-variable
-
+ #sample_xml = """
+ #
+ #
+ # Two houses, ...
+ #
+ #
+ #""".format(org=ORG, course=COURSE)
past_end = (datetime.now() - timedelta(days=12)).strftime("%Y-%m-%dT%H:%M:00")
future_end = (datetime.now() + timedelta(days=12)).strftime("%Y-%m-%dT%H:%M:00")
- self.past_show_certs = get_dummy_course(
- "2012-01-01T12:00",
- end=past_end,
- certs=CertificatesDisplayBehaviors.EARLY_NO_INFO
- )
- self.past_show_certs_no_info = get_dummy_course(
- "2012-01-01T12:00",
- end=past_end,
- certs=CertificatesDisplayBehaviors.EARLY_NO_INFO
- )
- self.past_noshow_certs = get_dummy_course(
- "2012-01-01T12:00",
- end=past_end,
- certs=CertificatesDisplayBehaviors.END
- )
-
- self.future_show_certs_no_info = get_dummy_course(
- "2012-01-01T12:00",
- end=future_end,
- certs=CertificatesDisplayBehaviors.EARLY_NO_INFO
- )
- self.future_noshow_certs = get_dummy_course(
- "2012-01-01T12:00",
- end=future_end,
- certs=CertificatesDisplayBehaviors.END
- )
+ self.past_show_certs = get_dummy_course("2012-01-01T12:00", end=past_end, certs='early_with_info')
+ self.past_show_certs_no_info = get_dummy_course("2012-01-01T12:00", end=past_end, certs='early_no_info')
+ self.past_noshow_certs = get_dummy_course("2012-01-01T12:00", end=past_end, certs='end')
+ self.future_show_certs = get_dummy_course("2012-01-01T12:00", end=future_end, certs='early_with_info')
+ self.future_show_certs_no_info = get_dummy_course("2012-01-01T12:00", end=future_end, certs='early_no_info')
+ self.future_noshow_certs = get_dummy_course("2012-01-01T12:00", end=future_end, certs='end')
+ #self.past_show_certs = system.process_xml(sample_xml.format(end=past_end, cert=True))
+ #self.past_noshow_certs = system.process_xml(sample_xml.format(end=past_end, cert=False))
+ #self.future_show_certs = system.process_xml(sample_xml.format(end=future_end, cert=True))
+ #self.future_noshow_certs = system.process_xml(sample_xml.format(end=future_end, cert=False))
def test_has_ended(self):
"""Check that has_ended correctly tells us when a course is over."""
assert self.past_show_certs.has_ended()
assert self.past_show_certs_no_info.has_ended()
assert self.past_noshow_certs.has_ended()
+ assert not self.future_show_certs.has_ended()
assert not self.future_show_certs_no_info.has_ended()
assert not self.future_noshow_certs.has_ended()
@@ -149,6 +143,7 @@ class HasEndedMayCertifyTestCase(unittest.TestCase):
assert self.past_show_certs.may_certify()
assert self.past_noshow_certs.may_certify()
assert self.past_show_certs_no_info.may_certify()
+ assert self.future_show_certs.may_certify()
assert self.future_show_certs_no_info.may_certify()
assert not self.future_noshow_certs.may_certify()
@@ -416,6 +411,14 @@ class CourseBlockTestCase(unittest.TestCase):
"""
assert self.course.number == COURSE
+ def test_set_default_certificate_available_date(self):
+ """
+ The certificate_available_date field should default to two days
+ after the course end date.
+ """
+ expected_certificate_available_date = self.course.end + timedelta(days=2)
+ assert expected_certificate_available_date == self.course.certificate_available_date
+
class ProctoringProviderTestCase(unittest.TestCase):
"""
diff --git a/lms/djangoapps/certificates/api.py b/lms/djangoapps/certificates/api.py
index 5ba569a8f5..a08d964054 100644
--- a/lms/djangoapps/certificates/api.py
+++ b/lms/djangoapps/certificates/api.py
@@ -47,7 +47,6 @@ from lms.djangoapps.certificates.utils import (
has_html_certificates_enabled as _has_html_certificates_enabled
)
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
-from xmodule.data import CertificatesDisplayBehaviors
log = logging.getLogger("edx.certificate")
User = get_user_model()
@@ -268,7 +267,6 @@ def certificate_downloadable_status(student, course_key):
if (
not certificates_viewable_for_course(course_overview) and
CertificateStatuses.is_passing_status(current_status['status']) and
- course_overview.certificates_display_behavior == CertificatesDisplayBehaviors.END_WITH_DATE and
course_overview.certificate_available_date
):
response_data['earned_but_not_available'] = True
@@ -596,18 +594,17 @@ def certificates_viewable_for_course(course):
if course.self_paced:
return True
if (
- course.certificates_display_behavior == CertificatesDisplayBehaviors.EARLY_NO_INFO
+ course.certificates_display_behavior in ('early_with_info', 'early_no_info')
or course.certificates_show_before_end
):
return True
if (
- course.certificates_display_behavior == CertificatesDisplayBehaviors.END_WITH_DATE
- and course.certificate_available_date
+ course.certificate_available_date
and course.certificate_available_date <= datetime.now(UTC)
):
return True
if (
- course.certificates_display_behavior == CertificatesDisplayBehaviors.END
+ course.certificate_available_date is None
and course.has_ended()
):
return True
diff --git a/lms/djangoapps/certificates/tests/test_api.py b/lms/djangoapps/certificates/tests/test_api.py
index 22e6e5050a..b3d18f89cb 100644
--- a/lms/djangoapps/certificates/tests/test_api.py
+++ b/lms/djangoapps/certificates/tests/test_api.py
@@ -19,7 +19,6 @@ from freezegun import freeze_time
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locator import CourseLocator
from testfixtures import LogCapture
-from xmodule.data import CertificatesDisplayBehaviors
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
@@ -225,31 +224,19 @@ class CertificateDownloadableStatusTests(WebCertificateTestMixin, ModuleStoreTes
'uuid': cert_status['uuid']}
@ddt.data(
- (True, timedelta(days=2), CertificatesDisplayBehaviors.END_WITH_DATE, True, None),
- (False, -timedelta(days=2), CertificatesDisplayBehaviors.EARLY_NO_INFO, True, None),
- (False, timedelta(days=2), CertificatesDisplayBehaviors.EARLY_NO_INFO, True, None),
- (False, -timedelta(days=2), CertificatesDisplayBehaviors.END, True, None),
- (False, timedelta(days=2), CertificatesDisplayBehaviors.END, True, None),
- (False, -timedelta(days=2), CertificatesDisplayBehaviors.END_WITH_DATE, True, None),
- (False, timedelta(days=2), CertificatesDisplayBehaviors.END_WITH_DATE, False, True),
+ (False, timedelta(days=2), False, True),
+ (False, -timedelta(days=2), True, None),
+ (True, timedelta(days=2), True, None)
)
@ddt.unpack
@patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': True})
- def test_cert_api_return(
- self,
- self_paced,
- cert_avail_delta,
- certificates_display_behavior,
- cert_downloadable_status,
- earned_but_not_available
- ):
+ def test_cert_api_return(self, self_paced, cert_avail_delta, cert_downloadable_status, earned_but_not_available):
"""
Test 'downloadable status'
"""
cert_avail_date = datetime.now(pytz.UTC) + cert_avail_delta
self.course.self_paced = self_paced
self.course.certificate_available_date = cert_avail_date
- self.course.certificates_display_behavior = certificates_display_behavior
self.course.save()
self._setup_course_certificate()
diff --git a/lms/djangoapps/certificates/tests/test_views.py b/lms/djangoapps/certificates/tests/test_views.py
index 8caf5c593c..7d862cb2f0 100644
--- a/lms/djangoapps/certificates/tests/test_views.py
+++ b/lms/djangoapps/certificates/tests/test_views.py
@@ -24,7 +24,6 @@ from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFact
from lms.djangoapps.certificates.utils import get_certificate_url
from openedx.core.djangoapps.site_configuration.tests.test_util import with_site_configuration
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
-from xmodule.data import CertificatesDisplayBehaviors
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
@@ -212,8 +211,7 @@ class CertificatesViewsSiteTests(ModuleStoreTestCase):
org='testorg',
number='run1',
display_name='refundable course',
- certificate_available_date=datetime.datetime.today() - datetime.timedelta(days=1),
- certificates_display_behavior=CertificatesDisplayBehaviors.END_WITH_DATE
+ certificate_available_date=datetime.datetime.today() - datetime.timedelta(days=1)
)
self.course.cert_html_view_enabled = True
self.course.save()
diff --git a/lms/djangoapps/certificates/tests/test_webview_views.py b/lms/djangoapps/certificates/tests/test_webview_views.py
index a389e297a9..9d1c1df083 100644
--- a/lms/djangoapps/certificates/tests/test_webview_views.py
+++ b/lms/djangoapps/certificates/tests/test_webview_views.py
@@ -50,7 +50,6 @@ from openedx.core.djangoapps.site_configuration.tests.test_util import (
from openedx.core.djangolib.js_utils import js_escaped_string
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
from openedx.core.lib.tests.assertions.events import assert_event_matches
-from xmodule.data import CertificatesDisplayBehaviors
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
@@ -82,7 +81,6 @@ class CommonCertificatesTestCase(ModuleStoreTestCase):
number='run1',
display_name='refundable course',
certificate_available_date=datetime.datetime.today() - datetime.timedelta(days=1),
- certificates_display_behavior=CertificatesDisplayBehaviors.END_WITH_DATE
)
self.course_id = self.course.location.course_key
self.user = UserFactory.create(
diff --git a/lms/djangoapps/certificates/views/webview.py b/lms/djangoapps/certificates/views/webview.py
index ccc201d1d6..2fc9ee85d0 100644
--- a/lms/djangoapps/certificates/views/webview.py
+++ b/lms/djangoapps/certificates/views/webview.py
@@ -50,7 +50,6 @@ from openedx.core.djangoapps.lang_pref.api import get_closest_released_language
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.lib.courses import course_image_url
from openedx.core.lib.courses import get_course_by_id
-from xmodule.data import CertificatesDisplayBehaviors
log = logging.getLogger(__name__)
_ = translation.ugettext
@@ -333,14 +332,8 @@ def _get_user_certificate(request, user, course_key, course, preview_mode=None):
if preview_mode:
# certificate is being previewed from studio
if request.user.has_perm(PREVIEW_CERTIFICATES, course):
- if (
- course.certificates_display_behavior == CertificatesDisplayBehaviors.END_WITH_DATE
- and course.certificate_available_date
- and not course.self_paced
- ):
+ if course.certificate_available_date and not course.self_paced:
modified_date = course.certificate_available_date
- elif course.certificates_display_behavior == CertificatesDisplayBehaviors.END:
- modified_date = course.end
else:
modified_date = datetime.now().date()
user_certificate = GeneratedCertificate(
diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py
index e5fc05696d..a8def5309a 100644
--- a/lms/djangoapps/courseware/tests/test_views.py
+++ b/lms/djangoapps/courseware/tests/test_views.py
@@ -99,7 +99,6 @@ from common.djangoapps.util.tests.test_date_utils import fake_pgettext, fake_uge
from common.djangoapps.util.url import reload_django_url_config
from common.djangoapps.util.views import ensure_valid_course_key
from xmodule.course_module import COURSE_VISIBILITY_PRIVATE, COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE
-from xmodule.data import CertificatesDisplayBehaviors
from xmodule.graders import ShowCorrectness
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
@@ -1289,7 +1288,6 @@ class ProgressPageBaseTests(ModuleStoreTestCase):
grade_cutoffs={'çü†øƒƒ': 0.75, 'Pass': 0.5},
end=datetime.now(),
certificate_available_date=datetime.now(UTC),
- certificates_display_behavior=CertificatesDisplayBehaviors.END_WITH_DATE,
**options
)
diff --git a/lms/templates/dashboard/_dashboard_certificate_information.html b/lms/templates/dashboard/_dashboard_certificate_information.html
index cae74d2d45..56a9a06c51 100644
--- a/lms/templates/dashboard/_dashboard_certificate_information.html
+++ b/lms/templates/dashboard/_dashboard_certificate_information.html
@@ -5,7 +5,6 @@ from django.utils.translation import ugettext as _
from openedx.core.djangolib.markup import HTML, Text
from common.djangoapps.course_modes.models import CourseMode
from lms.djangoapps.certificates.data import CertificateStatuses
-from xmodule.data import CertificatesDisplayBehaviors
%>
<%namespace name='static' file='../static_content.html'/>
@@ -37,12 +36,7 @@ else:
<%
- if course_overview.certificates_display_behavior == CertificatesDisplayBehaviors.END_WITH_DATE:
- certificate_available_date_string = course_overview.certificate_available_date.strftime('%Y-%m-%dT%H:%M:%S%z')
- elif course_overview.certificates_display_behavior == CertificatesDisplayBehaviors.END:
- certificate_available_date_string = course_overview.end.strftime('%Y-%m-%dT%H:%M:%S%z')
- else:
- raise Exception(course_overview.certificate_available_date, course_overview.certificates_display_behavior)
+ certificate_available_date_string = course_overview.certificate_available_date.strftime('%Y-%m-%dT%H:%M:%S%z')
container_string = _("Your grade and certificate will be ready after {date}.")
format = 'shortDate'
%>
diff --git a/openedx/core/djangoapps/certificates/api.py b/openedx/core/djangoapps/certificates/api.py
index a320755952..68f7a326d8 100644
--- a/openedx/core/djangoapps/certificates/api.py
+++ b/openedx/core/djangoapps/certificates/api.py
@@ -11,7 +11,6 @@ from lms.djangoapps.certificates import api as certs_api
from lms.djangoapps.certificates.data import CertificateStatuses
from openedx.core.djangoapps.certificates.config import waffle
from common.djangoapps.student.models import CourseEnrollment
-from xmodule.data import CertificatesDisplayBehaviors
log = logging.getLogger(__name__)
@@ -57,10 +56,7 @@ def can_show_certificate_available_date_field(course):
def _course_uses_available_date(course):
- return (
- can_show_certificate_available_date_field(course)
- and course.certificates_display_behavior == CertificatesDisplayBehaviors.END_WITH_DATE
- )
+ return can_show_certificate_available_date_field(course) and course.certificate_available_date
def available_date_for_certificate(course, certificate, certificate_available_date=None):
diff --git a/openedx/core/djangoapps/certificates/tests/test_api.py b/openedx/core/djangoapps/certificates/tests/test_api.py
index 15c607391e..1e7e933146 100644
--- a/openedx/core/djangoapps/certificates/tests/test_api.py
+++ b/openedx/core/djangoapps/certificates/tests/test_api.py
@@ -11,11 +11,10 @@ from edx_toggles.toggles import LegacyWaffleSwitch
from edx_toggles.toggles.testutils import override_waffle_switch
from common.djangoapps.course_modes.models import CourseMode
-from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
from openedx.core.djangoapps.certificates import api
from openedx.core.djangoapps.certificates.config import waffle as certs_waffle
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
-from xmodule.data import CertificatesDisplayBehaviors
+from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
# TODO: Copied from lms.djangoapps.certificates.models,
@@ -168,7 +167,6 @@ class CertificatesApiTestCase(TestCase):
# With an available date set in the past, both return the available date (if configured)
self.course.certificate_available_date = datetime(2017, 2, 1, tzinfo=pytz.UTC)
- self.course.certificates_display_behavior = CertificatesDisplayBehaviors.END_WITH_DATE
maybe_avail = self.course.certificate_available_date if uses_avail_date else self.certificate.modified_date
assert maybe_avail == api.available_date_for_certificate(self.course, self.certificate)
assert maybe_avail == api.display_date_for_certificate(self.course, self.certificate)
diff --git a/openedx/core/djangoapps/content/course_overviews/migrations/0026_auto_20210615_1706.py b/openedx/core/djangoapps/content/course_overviews/migrations/0026_auto_20210615_1706.py
deleted file mode 100644
index 4551bee620..0000000000
--- a/openedx/core/djangoapps/content/course_overviews/migrations/0026_auto_20210615_1706.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# Generated by Django 2.2.24 on 2021-06-15 17:06
-
-from django.db import migrations, models
-import xmodule.data
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('course_overviews', '0025_auto_20210702_1602'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='courseoverview',
- name='certificates_display_behavior',
- field=models.TextField(choices=[('end', 'END'), ('end_with_date', 'END_WITH_DATE'), ('early_no_info', 'EARLY_NO_INFO')], default=xmodule.data.CertificatesDisplayBehaviors('end'), null=True),
- ),
- migrations.AlterField(
- model_name='historicalcourseoverview',
- name='certificates_display_behavior',
- field=models.TextField(choices=[('end', 'END'), ('end_with_date', 'END_WITH_DATE'), ('early_no_info', 'EARLY_NO_INFO')], default=xmodule.data.CertificatesDisplayBehaviors('end'), null=True),
- ),
- ]
diff --git a/openedx/core/djangoapps/content/course_overviews/models.py b/openedx/core/djangoapps/content/course_overviews/models.py
index 28f0385d39..16936ba501 100644
--- a/openedx/core/djangoapps/content/course_overviews/models.py
+++ b/openedx/core/djangoapps/content/course_overviews/models.py
@@ -32,11 +32,11 @@ from openedx.core.lib.cache_utils import request_cached, RequestCache
from common.djangoapps.static_replace.models import AssetBaseUrlConfig
from xmodule import block_metadata_utils, course_metadata_utils
from xmodule.course_module import DEFAULT_START_DATE, CourseBlock
-from xmodule.data import CertificatesDisplayBehaviors
from xmodule.error_module import ErrorBlock
from xmodule.modulestore.django import modulestore
from xmodule.tabs import CourseTab
+
log = logging.getLogger(__name__)
@@ -56,10 +56,6 @@ class CourseOverview(TimeStampedModel):
course catalog (courses to enroll in)
course about (meta data about the course)
- When you bump the VERSION you will invalidate all existing course overviews. This
- will cause a slew of modulestore reads as each course needs to be re-cached into
- the course overview.
-
.. no_pii:
"""
@@ -67,7 +63,7 @@ class CourseOverview(TimeStampedModel):
app_label = 'course_overviews'
# IMPORTANT: Bump this whenever you modify this model and/or add a migration.
- VERSION = 14
+ VERSION = 13
# Cache entry versioning.
version = IntegerField()
@@ -100,11 +96,7 @@ class CourseOverview(TimeStampedModel):
end_of_course_survey_url = TextField(null=True)
# Certification data
- certificates_display_behavior = TextField(
- null=True,
- choices=[(choice.value, choice.name) for choice in CertificatesDisplayBehaviors],
- default=CertificatesDisplayBehaviors.END
- )
+ certificates_display_behavior = TextField(null=True)
certificates_show_before_end = BooleanField(default=False)
cert_html_view_enabled = BooleanField(default=False)
has_any_active_web_certificate = BooleanField(default=False)
@@ -219,15 +211,13 @@ class CourseOverview(TimeStampedModel):
course_overview.course_image_url = course_image_url(course)
course_overview.social_sharing_url = course.social_sharing_url
- updated_display_behavior, updated_available_date = cls.validate_certificate_settings(course)
-
- course_overview.certificates_display_behavior = updated_display_behavior
- course_overview.certificate_available_date = updated_available_date
+ course_overview.certificates_display_behavior = course.certificates_display_behavior
course_overview.certificates_show_before_end = course.certificates_show_before_end
course_overview.cert_html_view_enabled = course.cert_html_view_enabled
course_overview.has_any_active_web_certificate = (get_active_web_certificate(course) is not None)
course_overview.cert_name_short = course.cert_name_short
course_overview.cert_name_long = course.cert_name_long
+ course_overview.certificate_available_date = course.certificate_available_date
course_overview.lowest_passing_grade = lowest_passing_grade
course_overview.end_of_course_survey_url = course.end_of_course_survey_url
@@ -906,35 +896,6 @@ class CourseOverview(TimeStampedModel):
"""
return self._original_course.edxnotes_visibility
- @staticmethod
- def validate_certificate_settings(course):
- """
- Take a course and returns validated certificate display settings
-
- Arguments:
- course (CourseBlock): any course descriptor object
-
- Returns:
- tuple[str, str]: updated certificates_display_behavior, updated certificate_available_date
- None
- """
- # Backwards compatibility for existing courses that set availability date, didn't set behavior,
- # and expect availability date to be used
- certificates_display_behavior = course.certificates_display_behavior
- certificate_available_date = course.certificate_available_date
-
- if certificates_display_behavior == "" and certificate_available_date:
- certificates_display_behavior = CertificatesDisplayBehaviors.END_WITH_DATE
-
- if not CertificatesDisplayBehaviors.includes_value(certificates_display_behavior):
- certificates_display_behavior = CertificatesDisplayBehaviors.END
-
- # Null the date if it's not going to be used
- if certificates_display_behavior != CertificatesDisplayBehaviors.END_WITH_DATE:
- certificate_available_date = None
-
- return (certificates_display_behavior, certificate_available_date)
-
def __str__(self):
"""Represent ourselves with the course key."""
return str(self.id)
diff --git a/openedx/core/djangoapps/content/course_overviews/tests/test_signals.py b/openedx/core/djangoapps/content/course_overviews/tests/test_signals.py
index 690a53f074..aea25829fd 100644
--- a/openedx/core/djangoapps/content/course_overviews/tests/test_signals.py
+++ b/openedx/core/djangoapps/content/course_overviews/tests/test_signals.py
@@ -2,21 +2,16 @@
import datetime
from unittest.mock import patch
-from collections import namedtuple
import pytest
import ddt
-from xmodule.data import CertificatesDisplayBehaviors
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls
from ..models import CourseOverview
-# represents a change of a course overview field. Used to avoid confusing indicies
-Change = namedtuple("Change", ["field_name", "initial_value", "changed_value"])
-
@ddt.ddt
class CourseOverviewSignalsTestCase(ModuleStoreTestCase):
@@ -76,12 +71,8 @@ class CourseOverviewSignalsTestCase(ModuleStoreTestCase):
self.store.delete_course(course.id, ModuleStoreEnum.UserID.test)
CourseOverview.get_from_id(course.id)
- def assert_changed_signal_sent(self, changes, mock_signal): # lint-amnesty, pylint: disable=missing-function-docstring
-
- course = CourseFactory.create(
- emit_signals=True,
- **{change.field_name: change.initial_value for change in changes}
- )
+ def assert_changed_signal_sent(self, field_name, initial_value, changed_value, mock_signal): # lint-amnesty, pylint: disable=missing-function-docstring
+ course = CourseFactory.create(emit_signals=True, **{field_name: initial_value})
# changing display name doesn't fire the signal
course.display_name = course.display_name + 'changed'
@@ -89,27 +80,18 @@ class CourseOverviewSignalsTestCase(ModuleStoreTestCase):
assert not mock_signal.called
# changing the given field fires the signal
- for change in changes:
- setattr(course, change.field_name, change.changed_value)
+ setattr(course, field_name, changed_value)
self.store.update_item(course, ModuleStoreEnum.UserID.test)
assert mock_signal.called
@patch('openedx.core.djangoapps.content.course_overviews.signals.COURSE_START_DATE_CHANGED.send')
def test_start_changed(self, mock_signal):
- self.assert_changed_signal_sent([Change('start', self.TODAY, self.NEXT_WEEK)], mock_signal)
+ self.assert_changed_signal_sent('start', self.TODAY, self.NEXT_WEEK, mock_signal)
@patch('openedx.core.djangoapps.content.course_overviews.signals.COURSE_PACING_CHANGED.send')
def test_pacing_changed(self, mock_signal):
- self.assert_changed_signal_sent([Change('self_paced', True, False)], mock_signal)
+ self.assert_changed_signal_sent('self_paced', True, False, mock_signal)
@patch('openedx.core.djangoapps.content.course_overviews.signals.COURSE_CERT_DATE_CHANGE.send_robust')
def test_cert_date_changed(self, mock_signal):
- changes = [
- Change("certificate_available_date", self.TODAY, self.NEXT_WEEK),
- Change(
- "certificates_display_behavior",
- CertificatesDisplayBehaviors.END,
- CertificatesDisplayBehaviors.END_WITH_DATE
- )
- ]
- self.assert_changed_signal_sent(changes, mock_signal)
+ self.assert_changed_signal_sent('certificate_available_date', self.TODAY, self.NEXT_WEEK, mock_signal)
diff --git a/openedx/core/djangoapps/courseware_api/tests/test_views.py b/openedx/core/djangoapps/courseware_api/tests/test_views.py
index 79f062b944..c9dd69d040 100644
--- a/openedx/core/djangoapps/courseware_api/tests/test_views.py
+++ b/openedx/core/djangoapps/courseware_api/tests/test_views.py
@@ -2,7 +2,7 @@
Tests for courseware API
"""
import unittest
-from datetime import datetime, timedelta
+from datetime import datetime
from urllib.parse import urlencode
from typing import Optional
@@ -39,7 +39,6 @@ from common.djangoapps.student.roles import CourseInstructorRole
from common.djangoapps.student.tests.factories import CourseEnrollmentCelebrationFactory, UserFactory
from openedx.core.djangoapps.agreements.api import create_integrity_signature
from openedx.core.djangoapps.agreements.toggles import ENABLE_INTEGRITY_SIGNATURE
-from xmodule.data import CertificatesDisplayBehaviors
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import ItemFactory, ToyCourseFactory
@@ -47,8 +46,6 @@ from xmodule.modulestore.tests.factories import ItemFactory, ToyCourseFactory
User = get_user_model()
-_NEXT_WEEK = datetime.now() + timedelta(days=7)
-
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class BaseCoursewareTests(SharedModuleStoreTestCase):
@@ -67,8 +64,6 @@ class BaseCoursewareTests(SharedModuleStoreTestCase):
enrollment_end=datetime(2028, 1, 1, 1, 1, 1),
emit_signals=True,
modulestore=cls.store,
- certificate_available_date=_NEXT_WEEK,
- certificates_display_behavior=CertificatesDisplayBehaviors.END_WITH_DATE
)
cls.chapter = ItemFactory(parent=cls.course, category='chapter')
cls.sequence = ItemFactory(parent=cls.chapter, category='sequential', display_name='sequence')
@@ -152,7 +147,6 @@ class CourseApiTestViews(BaseCoursewareTests, MasqueradeMixin):
CourseEnrollment.enroll(self.user, self.course.id, enrollment_mode)
response = self.client.get(self.url)
-
assert response.status_code == 200
if enrollment_mode:
enrollment = response.data['enrollment']
diff --git a/openedx/core/djangoapps/models/course_details.py b/openedx/core/djangoapps/models/course_details.py
index bdb01bae63..748e90b591 100644
--- a/openedx/core/djangoapps/models/course_details.py
+++ b/openedx/core/djangoapps/models/course_details.py
@@ -48,8 +48,6 @@ class CourseDetails:
self.end_date = None # 'end'
self.enrollment_start = None
self.enrollment_end = None
- self.certificate_available_date = None
- self.certificates_display_behavior = None
self.syllabus = None # a pdf file asset
self.title = ""
self.subtitle = ""
@@ -110,8 +108,7 @@ class CourseDetails:
course_details = cls(course_key.org, course_key.course, course_key.run)
course_details.start_date = course_descriptor.start
course_details.end_date = course_descriptor.end
- course_details.certificate_available_date = course_descriptor.certificate_available_date
- course_details.certificates_display_behavior = course_descriptor.certificates_display_behavior
+ course_details.certificate_available_date = course_descriptor.certificate_available_date # lint-amnesty, pylint: disable=attribute-defined-outside-init
course_details.enrollment_start = course_descriptor.enrollment_start
course_details.enrollment_end = course_descriptor.enrollment_end
course_details.pre_requisite_courses = course_descriptor.pre_requisite_courses
@@ -247,13 +244,6 @@ class CourseDetails:
dirty = True
descriptor.certificate_available_date = converted
- if (
- 'certificates_display_behavior' in jsondict
- and jsondict['certificates_display_behavior'] != descriptor.certificates_display_behavior
- ):
- descriptor.certificates_display_behavior = jsondict['certificates_display_behavior']
- dirty = True
-
if 'course_image_name' in jsondict and jsondict['course_image_name'] != descriptor.course_image:
descriptor.course_image = jsondict['course_image_name']
dirty = True
diff --git a/openedx/core/djangoapps/programs/tests/test_tasks.py b/openedx/core/djangoapps/programs/tests/test_tasks.py
index 911489d03a..0f43629ba9 100644
--- a/openedx/core/djangoapps/programs/tests/test_tasks.py
+++ b/openedx/core/djangoapps/programs/tests/test_tasks.py
@@ -30,7 +30,6 @@ from openedx.core.djangoapps.oauth_dispatch.tests.factories import ApplicationFa
from openedx.core.djangoapps.programs import tasks
from openedx.core.djangoapps.site_configuration.tests.factories import SiteConfigurationFactory, SiteFactory
from openedx.core.djangolib.testing.utils import skip_unless_lms
-from xmodule.data import CertificatesDisplayBehaviors
log = logging.getLogger(__name__)
@@ -521,7 +520,6 @@ class AwardCourseCertificatesTestCase(CredentialsApiConfigMixin, TestCase):
self.course = CourseOverviewFactory.create(
self_paced=True, # Any option to allow the certificate to be viewable for the course
certificate_available_date=self.available_date,
- certificates_display_behavior=CertificatesDisplayBehaviors.END_WITH_DATE
)
self.student = UserFactory.create(username='test-student')
# Instantiate the Certificate first so that the config doesn't execute issuance
diff --git a/openedx/core/djangoapps/programs/tests/test_utils.py b/openedx/core/djangoapps/programs/tests/test_utils.py
index 4418b9b286..c6f9125125 100644
--- a/openedx/core/djangoapps/programs/tests/test_utils.py
+++ b/openedx/core/djangoapps/programs/tests/test_utils.py
@@ -51,7 +51,6 @@ from openedx.core.djangoapps.site_configuration.tests.factories import SiteFacto
from openedx.core.djangolib.testing.utils import skip_unless_lms
from common.djangoapps.student.tests.factories import AnonymousUserFactory, CourseEnrollmentFactory, UserFactory
from common.djangoapps.util.date_utils import strftime_localized
-from xmodule.data import CertificatesDisplayBehaviors
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase, SharedModuleStoreTestCase, TEST_DATA_SPLIT_MODULESTORE
@@ -500,7 +499,7 @@ class TestProgramProgressMeter(ModuleStoreTestCase):
end=two_days_ago,
self_paced=False,
certificate_available_date=tomorrow,
- certificates_display_behavior=CertificatesDisplayBehaviors.END_WITH_DATE
+ certificates_display_behavior='end'
)
third_course_run_key = str(course3.id)
@@ -608,7 +607,6 @@ class TestProgramProgressMeter(ModuleStoreTestCase):
# 3 certs, all available, program cert in the past/now
course3_overview = CourseOverview.get_from_id(course3.id)
course3_overview.certificate_available_date = yesterday
- course3_overview.certificates_display_behavior = CertificatesDisplayBehaviors.END_WITH_DATE
course3_overview.save()
meter = ProgramProgressMeter(self.site, self.user)
self._assert_progress(
@@ -624,7 +622,7 @@ class TestProgramProgressMeter(ModuleStoreTestCase):
def test_old_course_runs(self, mock_get_programs):
"""
Test that old course runs may exist for a program which do not exist in LMS.
- In that case, continue considering the course run to have been failed by the learner
+ In that case, continue considering the course run to've been failed by the learner
"""
course_run = CourseRunFactory.create()
course = CourseFactory.create(course_runs=[course_run])
diff --git a/openedx/features/learner_profile/tests/views/test_learner_profile.py b/openedx/features/learner_profile/tests/views/test_learner_profile.py
index 1dbcc6dbda..97d42a5cb9 100644
--- a/openedx/features/learner_profile/tests/views/test_learner_profile.py
+++ b/openedx/features/learner_profile/tests/views/test_learner_profile.py
@@ -21,7 +21,6 @@ from openedx.core.djangoapps.content.course_overviews.models import CourseOvervi
from openedx.core.djangoapps.site_configuration.tests.mixins import SiteMixin
from openedx.features.learner_profile.toggles import REDIRECT_TO_PROFILE_MICROFRONTEND
from openedx.features.learner_profile.views.learner_profile import learner_profile_context
-from xmodule.data import CertificatesDisplayBehaviors
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
@@ -223,8 +222,7 @@ class LearnerProfileViewTest(SiteMixin, UrlResetMixin, ModuleStoreTestCase):
"""
# add new course with certificate_available_date is future date.
course = CourseFactory.create(
- certificate_available_date=datetime.datetime.now() + datetime.timedelta(days=5),
- certificates_display_behavior=CertificatesDisplayBehaviors.END_WITH_DATE
+ certificate_available_date=datetime.datetime.now() + datetime.timedelta(days=5)
)
cert = self._create_certificate(course_key=course.id)
diff --git a/package-lock.json b/package-lock.json
index 92f2bbd431..17d331fbd0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -579,7 +579,7 @@
"aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
- "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
+ "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo="
},
"are-we-there-yet": {
"version": "1.1.5",