Skip to content

Commit

Permalink
feat: add functionality to create honor course modes through studio
Browse files Browse the repository at this point in the history
  • Loading branch information
mariajgrimaldi committed Feb 27, 2023
1 parent 25577e6 commit 98b1d0c
Show file tree
Hide file tree
Showing 11 changed files with 226 additions and 2 deletions.
12 changes: 12 additions & 0 deletions cms/djangoapps/contentstore/views/certificates.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,26 +400,38 @@ def certificates_list_handler(request, course_key_string):
handler_name='certificate_activation_handler',
course_key=course_key
)
course_mode_creation_url = reverse(
'course_modes_api:v1:course_modes_list',
args=(str(course_key),),
)
course_modes = [
mode.slug for mode in CourseMode.modes_for_course(
course_id=course.id, include_expired=True
) if mode.slug != 'audit'
]
# Check whether the audit mode associated with the course exists in database
has_audit_mode = CourseMode.objects.filter(course_id=course.id, mode_slug='audit')

has_certificate_modes = len(course_modes) > 0

enable_course_mode_creation = settings.FEATURES.get('ENABLE_COURSE_MODE_CREATION', False)

if has_certificate_modes:
certificate_web_view_url = get_lms_link_for_certificate_web_view(
course_key=course_key,
mode=course_modes[0] # CourseMode.modes_for_course returns default mode if doesn't find anyone.
)
enable_course_mode_creation = False
else:
certificate_web_view_url = None
is_active, certificates = CertificateManager.is_activated(course)
return render_to_response('certificates.html', {
'context_course': course,
'certificate_url': certificate_url,
'course_outline_url': course_outline_url,
'course_mode_creation_url': course_mode_creation_url,
'enable_course_mode_creation': enable_course_mode_creation and not has_audit_mode,
'course_id': str(course_key),
'upload_asset_url': upload_asset_url,
'certificates': certificates,
'has_certificate_modes': has_certificate_modes,
Expand Down
11 changes: 11 additions & 0 deletions cms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,17 @@
# in the LMS and CMS.
# .. toggle_tickets: 'https://github.com/open-craft/edx-platform/pull/429'
'DISABLE_UNENROLLMENT': False,

# .. toggle_name: ENABLE_COURSE_MODE_CREATION
# .. toggle_implementation: DjangoSetting
# .. toggle_default: False
# .. toggle_description: Set to True to enable course mode creation through studio.
# .. toggle_category: n/a
# .. toggle_use_cases: temporary
# .. toggle_creation_date: 2023-02-27
# .. toggle_target_removal_date: None
# .. toggle_tickets: https://openedx.atlassian.net/browse/OSPR-5906
'ENABLE_COURSE_MODE_CREATION': False,
}

# .. toggle_name: ENABLE_COPPA_COMPLIANCE
Expand Down
1 change: 1 addition & 0 deletions cms/static/cms/js/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
'js/factories/export',
'js/factories/group_configurations',
'js/certificates/factories/certificates_page_factory',
'js/certificates/factories/course_mode_factory',
'js/factories/index',
'js/factories/manage_users',
'js/factories/outline',
Expand Down
3 changes: 2 additions & 1 deletion cms/static/cms/js/spec/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,8 @@
'js/certificates/spec/views/certificate_details_spec',
'js/certificates/spec/views/certificate_editor_spec',
'js/certificates/spec/views/certificates_list_spec',
'js/certificates/spec/views/certificate_preview_spec'
'js/certificates/spec/views/certificate_preview_spec',
'js/certificates/spec/views/add_course_mode_spec'
];

i = 0;
Expand Down
15 changes: 15 additions & 0 deletions cms/static/js/certificates/factories/course_mode_factory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
define([
'jquery',
'js/certificates/views/add_course_mode'
],
function($, CourseModeHandler) {
'use strict';
return function(enableCourseModeCreation, courseModeCreationUrl, courseId) {
// Execute the page object's rendering workflow
new CourseModeHandler({
enableCourseModeCreation: enableCourseModeCreation,
courseModeCreationUrl: courseModeCreationUrl,
courseId: courseId
}).show();
};
});
89 changes: 89 additions & 0 deletions cms/static/js/certificates/spec/views/add_course_mode_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Jasmine Test Suite: Course modes creation

define([
'underscore',
'jquery',
'js/models/course',
'js/certificates/views/add_course_mode',
'common/js/spec_helpers/template_helpers',
'common/js/spec_helpers/view_helpers',
'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'
],
function(_, $, Course, AddCourseMode, TemplateHelpers, ViewHelpers, AjaxHelpers) {
'use strict';

var SELECTORS = {
addCourseMode: '.add-course-mode'
};

describe('Add Course Modes Spec:', function() {
beforeEach(function() {
window.course = new Course({
id: '5',
name: 'Course Name',
url_name: 'course_name',
org: 'course_org',
num: 'course_num',
revision: 'course_rev'
});
window.CMS.User = {isGlobalStaff: true, isCourseInstructor: true};

TemplateHelpers.installTemplate('course-modes', true);
appendSetFixtures('<div class="wrapper-certificates nav-actions"></div>');
appendSetFixtures('<p class="account-username">test</p>');
this.view = new AddCourseMode({
el: $('.wrapper-certificates'),
courseId: window.course.id,
courseModeCreationUrl: '/api/course_modes/v1/courses/' + window.course.id + '/',
enableCourseModeCreation: true
});
appendSetFixtures(this.view.render().el);
});

afterEach(function() {
delete window.course;
delete window.CMS.User;
});

describe('Add course modes', function() {
it('course mode creation event works fine', function() {
spyOn(this.view, 'addCourseMode');
this.view.delegateEvents();
this.view.$(SELECTORS.addCourseMode).click();
expect(this.view.addCourseMode).toHaveBeenCalled();
});

it('add course modes button works fine', function() {
var requests = AjaxHelpers.requests(this),
notificationSpy = ViewHelpers.createNotificationSpy();
this.view.$(SELECTORS.addCourseMode).click();
AjaxHelpers.expectJsonRequest(
requests,
'POST', '/api/course_modes/v1/courses/' + window.course.id + '/?username=test',
{
course_id: window.course.id,
mode_slug: 'honor',
mode_display_name: 'Honor',
currency: 'usd'
});
ViewHelpers.verifyNotificationShowing(notificationSpy, /Enabling honor course mode/);
});

it('enable course mode creation should be false when method "remove" called', function() {
this.view.remove();
expect(this.view.enableCourseModeCreation).toBe(false);
});

it('add course mode should be removed when method "remove" called', function() {
this.view.remove();
expect(this.view.el.innerHTML).toBe('');
});

it('method "show" should call the render function', function() {
spyOn(this.view, 'render');
this.view.show();
expect(this.view.render).toHaveBeenCalled();
});
});
});
});
70 changes: 70 additions & 0 deletions cms/static/js/certificates/views/add_course_mode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
define([
'underscore',
'gettext',
'js/views/baseview',
'common/js/components/views/feedback_notification',
'text!templates/course-modes.underscore',
'edx-ui-toolkit/js/utils/html-utils'
],
function(_, gettext, BaseView, NotificationView, CourseModes, HtmlUtils) {
'use strict';

var AddCourseMode = BaseView.extend({
el: $('.wrapper-certificates'),
events: {
'click .add-course-mode': 'addCourseMode'
},

initialize: function(options) {
this.enableCourseModeCreation = options.enableCourseModeCreation;
this.courseModeCreationUrl = options.courseModeCreationUrl;
this.courseId = options.courseId;
},

render: function() {
HtmlUtils.setHtml(this.$el, HtmlUtils.template(CourseModes)({
enableCourseModeCreation: this.enableCourseModeCreation,
courseModeCreationUrl: this.courseModeCreationUrl,
courseId: this.courseId
}));
return this;
},

addCourseMode: function() {
var notification = new NotificationView.Mini({
title: gettext('Enabling honor course mode')
});
var username = $('.account-username')[0].innerText;
$.ajax({
url: this.courseModeCreationUrl + '?username=' + username,
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify({
course_id: this.courseId,
mode_slug: 'honor',
mode_display_name: 'Honor',
currency: 'usd'
}),
type: 'POST',
beforeSend: function() {
notification.show();
},
success: function() {
notification.hide();
location.reload();
}
});
},

show: function() {
this.render();
},

remove: function() {
this.enableCourseModeCreation = false;
this.$el.empty();
return this;
}
});
return AddCourseMode;
});
11 changes: 10 additions & 1 deletion cms/templates/certificates.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<%block name="bodyclass">is-signedin course view-certificates</%block>

<%block name="header_extras">
% for template_name in ["certificate-details", "certificate-editor", "signatory-editor", "signatory-details", "basic-modal", "modal-button", "list", "upload-dialog", "certificate-web-preview", "signatory-actions"]:
% for template_name in ["certificate-details", "certificate-editor", "signatory-editor", "signatory-details", "basic-modal", "modal-button", "list", "upload-dialog", "certificate-web-preview", "signatory-actions", "course-modes"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="js/${template_name}.underscore" />
</script>
Expand Down Expand Up @@ -48,6 +48,15 @@
);
});
% endif
% if enable_course_mode_creation:
require(["js/certificates/factories/course_mode_factory"], function(CourseModeFactory) {
CourseModeFactory(
${enable_course_mode_creation | n, dump_js_escaped_json},
${course_mode_creation_url | n, dump_js_escaped_json},
${course_id | n, dump_js_escaped_json}
);
});
% endif
</%block>

<%block name="content">
Expand Down
6 changes: 6 additions & 0 deletions cms/templates/js/course-modes.underscore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div class="no-content">
<label for="add-course-mode"><%- gettext("Enable the honor mode for the course.") %></label>
<a href="#" class="button new-button add-course-mode">
<% if ( enableCourseModeCreation ) { %> <%- gettext("Enable honor mode") %> <% } %>
</a>
</div>
9 changes: 9 additions & 0 deletions cms/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,15 @@
path('api/val/v0/', include('edxval.urls')),
path('api/tasks/v0/', include('user_tasks.urls')),
path('accessibility', contentstore_views.accessibility, name='accessibility'),

# Course modes API for certificates generation
path(
'api/course_modes/',
include(
('common.djangoapps.course_modes.rest_api.urls', 'common.djangoapps.course_mods'),
namespace='course_modes_api',
)
),
]

if not settings.DISABLE_DEPRECATED_SIGNIN_URL:
Expand Down
1 change: 1 addition & 0 deletions webpack-config/file-lists.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module.exports = {
path.resolve(__dirname, '../cms/static/js/certificates/views/certificate_preview.js'),
path.resolve(__dirname, '../cms/static/js/certificates/views/signatory_details.js'),
path.resolve(__dirname, '../cms/static/js/certificates/views/signatory_editor.js'),
path.resolve(__dirname, '../cms/static/js/certificates/views/add_course_mode.js'),
path.resolve(__dirname, '../cms/static/js/views/active_video_upload_list.js'),
path.resolve(__dirname, '../cms/static/js/views/assets.js'),
path.resolve(__dirname, '../cms/static/js/views/course_video_settings.js'),
Expand Down

0 comments on commit 98b1d0c

Please sign in to comment.