Move redirection/enrollment to AccessView and trigger it using an event.
Ensure window.isExternal is loaded Don't set a default for terms of service For paid courses, add the course to the cart and redirect to the shopping cart view. Don't send form method and url as form data. Stub window.analytics in the access view test.
This commit is contained in:
@@ -14,7 +14,7 @@ from student.models import NonExistentCourseError, CourseEnrollmentException
|
||||
|
||||
|
||||
class EnrollmentUserThrottle(UserRateThrottle):
|
||||
rate = '50/second' # TODO Limit significantly after performance testing.
|
||||
rate = '50/second' # TODO Limit significantly after performance testing.
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
|
||||
@@ -1034,7 +1034,9 @@ instructor_dash_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/ins
|
||||
student_account_js = [
|
||||
'js/utils/rwd_header_footer.js',
|
||||
'js/utils/edx.utils.validate.js',
|
||||
'js/student_account/enrollment_interface.js',
|
||||
'js/src/utility.js',
|
||||
'js/student_account/enrollment.js',
|
||||
'js/student_account/shoppingcart.js',
|
||||
'js/student_account/models/LoginModel.js',
|
||||
'js/student_account/models/RegisterModel.js',
|
||||
'js/student_account/models/PasswordResetModel.js',
|
||||
|
||||
@@ -289,9 +289,13 @@
|
||||
exports: 'NotificationView',
|
||||
deps: ['backbone', 'jquery', 'underscore']
|
||||
},
|
||||
'js/student_account/enrollment_interface': {
|
||||
'js/student_account/enrollment': {
|
||||
exports: 'edx.student.account.EnrollmentInterface',
|
||||
deps: ['jquery', 'jquery.cookie', 'underscore', 'gettext']
|
||||
deps: ['jquery', 'jquery.cookie']
|
||||
},
|
||||
'js/student_account/shoppingcart': {
|
||||
exports: 'edx.student.account.ShoppingCartInterface',
|
||||
deps: ['jquery', 'jquery.cookie', 'underscore']
|
||||
},
|
||||
// Student account registration/login
|
||||
// Loaded explicitly until these are converted to RequireJS
|
||||
@@ -350,13 +354,16 @@
|
||||
'underscore',
|
||||
'backbone',
|
||||
'gettext',
|
||||
'utility',
|
||||
'js/student_account/views/LoginView',
|
||||
'js/student_account/views/PasswordResetView',
|
||||
'js/student_account/views/RegisterView',
|
||||
'js/student_account/models/LoginModel',
|
||||
'js/student_account/models/PasswordResetModel',
|
||||
'js/student_account/models/RegisterModel',
|
||||
'js/student_account/views/FormView'
|
||||
'js/student_account/views/FormView',
|
||||
'js/student_account/enrollment',
|
||||
'js/student_account/shoppingcart',
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -375,7 +382,8 @@
|
||||
'lms/include/js/spec/student_account/login_spec.js',
|
||||
'lms/include/js/spec/student_account/register_spec.js',
|
||||
'lms/include/js/spec/student_account/password_reset_spec.js',
|
||||
'lms/include/js/spec/student_account/enrollment_interface_spec.js',
|
||||
'lms/include/js/spec/student_account/enrollment_spec.js',
|
||||
'lms/include/js/spec/student_account/shoppingcart_spec.js',
|
||||
'lms/include/js/spec/student_profile/profile_spec.js'
|
||||
]);
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@ define([
|
||||
'js/common_helpers/template_helpers',
|
||||
'js/common_helpers/ajax_helpers',
|
||||
'js/student_account/views/AccessView',
|
||||
'js/student_account/views/FormView'
|
||||
], function($, TemplateHelpers, AjaxHelpers, AccessView) {
|
||||
'js/student_account/views/FormView',
|
||||
'js/student_account/enrollment',
|
||||
'js/student_account/shoppingcart'
|
||||
], function($, TemplateHelpers, AjaxHelpers, AccessView, FormView, EnrollmentInterface, ShoppingCartInterface) {
|
||||
describe('edx.student.account.AccessView', function() {
|
||||
'use strict';
|
||||
|
||||
@@ -51,7 +53,9 @@ define([
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
FORWARD_URL = '/courseware/next',
|
||||
COURSE_KEY = 'edx/DemoX/Fall';
|
||||
|
||||
var ajaxAssertAndRespond = function(url, requestIndex) {
|
||||
// Verify that the client contacts the server as expected
|
||||
@@ -77,6 +81,14 @@ define([
|
||||
platformName: 'edX'
|
||||
});
|
||||
|
||||
// Mock the redirect call
|
||||
spyOn( view, 'redirect' ).andCallFake( function() {} );
|
||||
|
||||
// Mock the enrollment and shopping cart interfaces
|
||||
spyOn( EnrollmentInterface, 'enroll' ).andCallFake( function() {} );
|
||||
spyOn( ShoppingCartInterface, 'addCourseToCart' ).andCallFake( function() {} );
|
||||
|
||||
// Initialize the subview
|
||||
ajaxAssertAndRespond(AJAX_INFO[mode].url);
|
||||
};
|
||||
|
||||
@@ -97,6 +109,20 @@ define([
|
||||
ajaxAssertAndRespond(AJAX_INFO[type].url, AJAX_INFO[type].requestIndex);
|
||||
};
|
||||
|
||||
/**
|
||||
* Simulate query string params.
|
||||
*
|
||||
* @param {object} params Parameters to set, each of which
|
||||
* should be prefixed with '?'
|
||||
*/
|
||||
var setFakeQueryParams = function( params ) {
|
||||
spyOn( $, 'url' ).andCallFake(function( requestedParam ) {
|
||||
if ( params.hasOwnProperty(requestedParam) ) {
|
||||
return params[requestedParam];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
setFixtures('<div id="login-and-registration-container"></div>');
|
||||
TemplateHelpers.installTemplate('templates/student_account/access');
|
||||
@@ -104,6 +130,11 @@ define([
|
||||
TemplateHelpers.installTemplate('templates/student_account/register');
|
||||
TemplateHelpers.installTemplate('templates/student_account/password_reset');
|
||||
TemplateHelpers.installTemplate('templates/student_account/form_field');
|
||||
|
||||
// Stub analytics tracking
|
||||
// TODO: use RequireJS to ensure that this is loaded correctly
|
||||
window.analytics = window.analytics || {};
|
||||
window.analytics.track = window.analytics.track || function() {};
|
||||
});
|
||||
|
||||
it('can initially display the login form', function() {
|
||||
@@ -143,14 +174,83 @@ define([
|
||||
view.resetPassword();
|
||||
|
||||
ajaxAssertAndRespond(
|
||||
AJAX_INFO['password_reset'].url,
|
||||
AJAX_INFO['password_reset'].requestIndex
|
||||
AJAX_INFO.password_reset.url,
|
||||
AJAX_INFO.password_reset.requestIndex
|
||||
);
|
||||
|
||||
// Verify that the password reset wrapper is populated
|
||||
expect($('#password-reset-wrapper')).not.toBeEmpty();
|
||||
});
|
||||
|
||||
it('enrolls the user on auth complete', function() {
|
||||
ajaxSpyAndInitialize(this, 'login');
|
||||
|
||||
// Simulate providing enrollment query string params
|
||||
setFakeQueryParams({
|
||||
'?enrollment_action': 'enroll',
|
||||
'?course_id': COURSE_KEY
|
||||
});
|
||||
|
||||
// Trigger auth complete on the login view
|
||||
view.subview.login.trigger('auth-complete');
|
||||
|
||||
// Expect that the view tried to enroll the student
|
||||
expect( EnrollmentInterface.enroll ).toHaveBeenCalledWith( COURSE_KEY );
|
||||
});
|
||||
|
||||
it('adds a white-label course to the shopping cart on auth complete', function() {
|
||||
ajaxSpyAndInitialize(this, 'register');
|
||||
|
||||
// Simulate providing "add to cart" query string params
|
||||
setFakeQueryParams({
|
||||
'?enrollment_action': 'add_to_cart',
|
||||
'?course_id': COURSE_KEY
|
||||
});
|
||||
|
||||
// Trigger auth complete on the register view
|
||||
view.subview.register.trigger('auth-complete');
|
||||
|
||||
// Expect that the view tried to add the course to the user's shopping cart
|
||||
expect( ShoppingCartInterface.addCourseToCart ).toHaveBeenCalledWith( COURSE_KEY );
|
||||
});
|
||||
|
||||
it('redirects the user to the dashboard on auth complete', function() {
|
||||
ajaxSpyAndInitialize(this, 'register');
|
||||
|
||||
// Trigger auth complete
|
||||
view.subview.register.trigger('auth-complete');
|
||||
|
||||
// Since we did not provide a ?next query param, expect a redirect to the dashboard.
|
||||
expect( view.redirect ).toHaveBeenCalledWith( '/dashboard' );
|
||||
});
|
||||
|
||||
it('redirects the user to the next page on auth complete', function() {
|
||||
ajaxSpyAndInitialize(this, 'register');
|
||||
|
||||
// Simulate providing a ?next query string parameter
|
||||
setFakeQueryParams({ '?next': FORWARD_URL });
|
||||
|
||||
// Trigger auth complete
|
||||
view.subview.register.trigger('auth-complete');
|
||||
|
||||
// Verify that we were redirected
|
||||
expect( view.redirect ).toHaveBeenCalledWith( FORWARD_URL );
|
||||
});
|
||||
|
||||
it('ignores redirect to external URLs', function() {
|
||||
ajaxSpyAndInitialize(this, 'register');
|
||||
|
||||
// Simulate providing a ?next query string parameter
|
||||
// that goes to an external URL
|
||||
setFakeQueryParams({ '?next': "http://www.example.com" });
|
||||
|
||||
// Trigger auth complete
|
||||
view.subview.register.trigger('auth-complete');
|
||||
|
||||
// Expect that we ignore the external URL and redirect to the dashboard
|
||||
expect( view.redirect ).toHaveBeenCalledWith( "/dashboard" );
|
||||
});
|
||||
|
||||
it('displays an error if a form definition could not be loaded', function() {
|
||||
// Spy on AJAX requests
|
||||
requests = AjaxHelpers.requests(this);
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
define(['js/student_account/enrollment_interface'],
|
||||
function(EnrollmentInterface) {
|
||||
describe("edx.student.account.EnrollmentInterface", function() {
|
||||
'use strict';
|
||||
|
||||
it('checks if a given course mode slug exists in an array of mode objects', function() {
|
||||
var courseModes = [ { slug: 'honor' }, { slug: 'professional' } ]
|
||||
|
||||
expect( EnrollmentInterface.modeInArray( courseModes, 'professional' ) ).toBe(true);
|
||||
expect( EnrollmentInterface.modeInArray( courseModes, 'audit' ) ).toBe(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
49
lms/static/js/spec/student_account/enrollment_spec.js
Normal file
49
lms/static/js/spec/student_account/enrollment_spec.js
Normal file
@@ -0,0 +1,49 @@
|
||||
define(['js/common_helpers/ajax_helpers', 'js/student_account/enrollment'],
|
||||
function( AjaxHelpers, EnrollmentInterface ) {
|
||||
'use strict';
|
||||
|
||||
describe( 'edx.student.account.EnrollmentInterface', function() {
|
||||
|
||||
var COURSE_KEY = 'edX/DemoX/Fall',
|
||||
ENROLL_URL = '/enrollment/v0/course/edX/DemoX/Fall',
|
||||
FORWARD_URL = '/course_modes/choose/edX/DemoX/Fall/';
|
||||
|
||||
beforeEach(function() {
|
||||
// Mock the redirect call
|
||||
spyOn(EnrollmentInterface, 'redirect').andCallFake(function() {});
|
||||
});
|
||||
|
||||
it('enrolls a user in a course', function() {
|
||||
// Spy on Ajax requests
|
||||
var requests = AjaxHelpers.requests( this );
|
||||
|
||||
// Attempt to enroll the user
|
||||
EnrollmentInterface.enroll( COURSE_KEY );
|
||||
|
||||
// Expect that the correct request was made to the server
|
||||
AjaxHelpers.expectRequest( requests, 'POST', ENROLL_URL );
|
||||
|
||||
// Simulate a successful response from the server
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
|
||||
// Verify that the user was redirected correctly
|
||||
expect( EnrollmentInterface.redirect ).toHaveBeenCalledWith( FORWARD_URL );
|
||||
});
|
||||
|
||||
it('redirects the user if enrollment fails', function() {
|
||||
// Spy on Ajax requests
|
||||
var requests = AjaxHelpers.requests( this );
|
||||
|
||||
// Attempt to enroll the user
|
||||
EnrollmentInterface.enroll( COURSE_KEY );
|
||||
|
||||
// Simulate an error response from the server
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
|
||||
// Verify that the user was still redirected
|
||||
expect(EnrollmentInterface.redirect).toHaveBeenCalledWith( FORWARD_URL );
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -12,6 +12,7 @@ define([
|
||||
var model = null,
|
||||
view = null,
|
||||
requests = null,
|
||||
authComplete = false,
|
||||
PLATFORM_NAME = 'edX',
|
||||
USER_DATA = {
|
||||
email: 'xsy@edx.org',
|
||||
@@ -72,7 +73,10 @@ define([
|
||||
|
||||
var createLoginView = function(test) {
|
||||
// Initialize the login model
|
||||
model = new LoginModel({ url: FORM_DESCRIPTION.submit_url });
|
||||
model = new LoginModel({}, {
|
||||
url: FORM_DESCRIPTION.submit_url,
|
||||
method: FORM_DESCRIPTION.method
|
||||
});
|
||||
|
||||
// Initialize the login view
|
||||
view = new LoginView({
|
||||
@@ -85,9 +89,10 @@ define([
|
||||
// Spy on AJAX requests
|
||||
requests = AjaxHelpers.requests(test);
|
||||
|
||||
// Mock out redirection logic
|
||||
spyOn(view, 'redirect').andCallFake(function() {
|
||||
return true;
|
||||
// Intercept events from the view
|
||||
authComplete = false;
|
||||
view.on("auth-complete", function() {
|
||||
authComplete = true;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -130,16 +135,16 @@ define([
|
||||
|
||||
// Verify that the client contacts the server with the expected data
|
||||
AjaxHelpers.expectRequest(
|
||||
requests, 'POST', FORM_DESCRIPTION.submit_url, $.param(
|
||||
$.extend({url: FORM_DESCRIPTION.submit_url}, USER_DATA)
|
||||
)
|
||||
requests, 'POST',
|
||||
FORM_DESCRIPTION.submit_url,
|
||||
$.param( USER_DATA )
|
||||
);
|
||||
|
||||
// Respond with status code 200
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
|
||||
// Verify that the user is redirected to the dashboard
|
||||
expect(view.redirect).toHaveBeenCalledWith('/dashboard');
|
||||
// Verify that auth-complete is triggered
|
||||
expect(authComplete).toBe(true);
|
||||
});
|
||||
|
||||
it('displays third-party auth login buttons', function() {
|
||||
@@ -175,6 +180,9 @@ define([
|
||||
|
||||
// Verify that submission errors are visible
|
||||
expect(view.$errors).not.toHaveClass('hidden');
|
||||
|
||||
// Expect auth complete NOT to have been triggered
|
||||
expect(authComplete).toBe(false);
|
||||
});
|
||||
|
||||
it('displays an error if the server returns an error while logging in', function() {
|
||||
@@ -186,9 +194,9 @@ define([
|
||||
// Simulate an error from the LMS servers
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
|
||||
// Expect that an error is displayed, and that we haven't been redirected
|
||||
// Expect that an error is displayed and that auth complete is not triggered
|
||||
expect(view.$errors).not.toHaveClass('hidden');
|
||||
expect(view.redirect).not.toHaveBeenCalled();
|
||||
expect(authComplete).toBe(false);
|
||||
|
||||
// If we try again and succeed, the error should go away
|
||||
submitForm();
|
||||
@@ -196,8 +204,9 @@ define([
|
||||
// This time, respond with status code 200
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
|
||||
// Expect that the error is hidden
|
||||
// Expect that the error is hidden and auth complete is triggered
|
||||
expect(view.$errors).toHaveClass('hidden');
|
||||
expect(authComplete).toBe(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,7 +30,10 @@ define([
|
||||
|
||||
var createPasswordResetView = function(that) {
|
||||
// Initialize the password reset model
|
||||
model = new PasswordResetModel({ url: FORM_DESCRIPTION.submit_url });
|
||||
model = new PasswordResetModel({}, {
|
||||
url: FORM_DESCRIPTION.submit_url,
|
||||
method: FORM_DESCRIPTION.method
|
||||
});
|
||||
|
||||
// Initialize the password reset view
|
||||
view = new PasswordResetView({
|
||||
@@ -77,10 +80,9 @@ define([
|
||||
|
||||
// Verify that the client contacts the server with the expected data
|
||||
AjaxHelpers.expectRequest(
|
||||
requests, 'POST', FORM_DESCRIPTION.submit_url, $.param({
|
||||
url: FORM_DESCRIPTION.submit_url,
|
||||
email: EMAIL
|
||||
})
|
||||
requests, 'POST',
|
||||
FORM_DESCRIPTION.submit_url,
|
||||
$.param({ email: EMAIL })
|
||||
);
|
||||
|
||||
// Respond with status code 200
|
||||
@@ -125,10 +127,10 @@ define([
|
||||
|
||||
// If we try again and succeed, the error should go away
|
||||
submitEmail();
|
||||
|
||||
|
||||
// This time, respond with status code 200
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
|
||||
|
||||
// Expect that the error is hidden
|
||||
expect(view.$errors).toHaveClass('hidden');
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ define([
|
||||
var model = null,
|
||||
view = null,
|
||||
requests = null,
|
||||
authComplete = false,
|
||||
PLATFORM_NAME = 'edX',
|
||||
USER_DATA = {
|
||||
email: 'xsy@edx.org',
|
||||
@@ -160,7 +161,10 @@ define([
|
||||
|
||||
var createRegisterView = function(that) {
|
||||
// Initialize the register model
|
||||
model = new RegisterModel({ url: FORM_DESCRIPTION.submit_url });
|
||||
model = new RegisterModel({}, {
|
||||
url: FORM_DESCRIPTION.submit_url,
|
||||
method: FORM_DESCRIPTION.method
|
||||
});
|
||||
|
||||
// Initialize the register view
|
||||
view = new RegisterView({
|
||||
@@ -173,9 +177,10 @@ define([
|
||||
// Spy on AJAX requests
|
||||
requests = AjaxHelpers.requests(that);
|
||||
|
||||
// Mock out redirection logic
|
||||
spyOn(view, 'redirect').andCallFake(function() {
|
||||
return true;
|
||||
// Intercept events from the view
|
||||
authComplete = false;
|
||||
view.on("auth-complete", function() {
|
||||
authComplete = true;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -225,16 +230,16 @@ define([
|
||||
|
||||
// Verify that the client contacts the server with the expected data
|
||||
AjaxHelpers.expectRequest(
|
||||
requests, 'POST', FORM_DESCRIPTION.submit_url, $.param(
|
||||
$.extend({url: FORM_DESCRIPTION.submit_url}, USER_DATA)
|
||||
)
|
||||
requests, 'POST',
|
||||
FORM_DESCRIPTION.submit_url,
|
||||
$.param( USER_DATA )
|
||||
);
|
||||
|
||||
// Respond with status code 200
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
|
||||
// Verify that the user is redirected to the dashboard
|
||||
expect(view.redirect).toHaveBeenCalledWith('/dashboard');
|
||||
// Verify that auth complete is triggered
|
||||
expect(authComplete).toBe(true);
|
||||
});
|
||||
|
||||
it('displays third-party auth registration buttons', function() {
|
||||
@@ -269,6 +274,9 @@ define([
|
||||
|
||||
// Verify that submission errors are visible
|
||||
expect(view.$errors).not.toHaveClass('hidden');
|
||||
|
||||
// Expect that auth complete is NOT triggered
|
||||
expect(authComplete).toBe(false);
|
||||
});
|
||||
|
||||
it('displays an error if the server returns an error while registering', function() {
|
||||
@@ -280,8 +288,9 @@ define([
|
||||
// Simulate an error from the LMS servers
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
|
||||
// Expect that an error is displayed
|
||||
// Expect that an error is displayed and that auth complete is NOT triggered
|
||||
expect(view.$errors).not.toHaveClass('hidden');
|
||||
expect(authComplete).toBe(false);
|
||||
|
||||
// If we try again and succeed, the error should go away
|
||||
submitForm();
|
||||
@@ -289,8 +298,9 @@ define([
|
||||
// This time, respond with status code 200
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
|
||||
// Expect that the error is hidden
|
||||
// Expect that the error is hidden and that auth complete is triggered
|
||||
expect(view.$errors).toHaveClass('hidden');
|
||||
expect(authComplete).toBe(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
48
lms/static/js/spec/student_account/shoppingcart_spec.js
Normal file
48
lms/static/js/spec/student_account/shoppingcart_spec.js
Normal file
@@ -0,0 +1,48 @@
|
||||
define(['js/common_helpers/ajax_helpers', 'js/student_account/shoppingcart'],
|
||||
function(AjaxHelpers, ShoppingCartInterface) {
|
||||
'use strict';
|
||||
|
||||
describe( 'edx.student.account.ShoppingCartInterface', function() {
|
||||
|
||||
var COURSE_KEY = "edX/DemoX/Fall",
|
||||
ADD_COURSE_URL = "/shoppingcart/add/course/edX/DemoX/Fall/",
|
||||
FORWARD_URL = "/shoppingcart/";
|
||||
|
||||
beforeEach(function() {
|
||||
// Mock the redirect call
|
||||
spyOn(ShoppingCartInterface, 'redirect').andCallFake(function() {});
|
||||
});
|
||||
|
||||
it('adds a course to the cart', function() {
|
||||
// Spy on Ajax requests
|
||||
var requests = AjaxHelpers.requests( this );
|
||||
|
||||
// Attempt to add a course to the cart
|
||||
ShoppingCartInterface.addCourseToCart( COURSE_KEY );
|
||||
|
||||
// Expect that the correct request was made to the server
|
||||
AjaxHelpers.expectRequest( requests, 'POST', ADD_COURSE_URL );
|
||||
|
||||
// Simulate a successful response from the server
|
||||
AjaxHelpers.respondWithJson( requests, {} );
|
||||
|
||||
// Expect that the user was redirected to the shopping cart
|
||||
expect( ShoppingCartInterface.redirect ).toHaveBeenCalledWith( FORWARD_URL );
|
||||
});
|
||||
|
||||
it('redirects the user on a server error', function() {
|
||||
// Spy on Ajax requests
|
||||
var requests = AjaxHelpers.requests( this );
|
||||
|
||||
// Attempt to add a course to the cart
|
||||
ShoppingCartInterface.addCourseToCart( COURSE_KEY );
|
||||
|
||||
// Simulate an error response from the server
|
||||
AjaxHelpers.respondWithError( requests );
|
||||
|
||||
// Expect that the user was redirected to the shopping cart
|
||||
expect( ShoppingCartInterface.redirect ).toHaveBeenCalledWith( FORWARD_URL );
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
63
lms/static/js/student_account/enrollment.js
Normal file
63
lms/static/js/student_account/enrollment.js
Normal file
@@ -0,0 +1,63 @@
|
||||
var edx = edx || {};
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
edx.student = edx.student || {};
|
||||
edx.student.account = edx.student.account || {};
|
||||
|
||||
edx.student.account.EnrollmentInterface = {
|
||||
|
||||
urls: {
|
||||
course: '/enrollment/v0/course/',
|
||||
trackSelection: '/course_modes/choose/'
|
||||
},
|
||||
|
||||
headers: {
|
||||
'X-CSRFToken': $.cookie('csrftoken')
|
||||
},
|
||||
|
||||
/**
|
||||
* Enroll a user in a course, then redirect the user
|
||||
* to the track selection page.
|
||||
* @param {string} courseKey Slash-separated course key.
|
||||
*/
|
||||
enroll: function( courseKey ) {
|
||||
$.ajax({
|
||||
url: this.courseEnrollmentUrl( courseKey ),
|
||||
type: 'POST',
|
||||
data: {},
|
||||
headers: this.headers,
|
||||
context: this
|
||||
}).always(function() {
|
||||
this.redirect( this.trackSelectionUrl( courseKey ) );
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Construct the URL to the track selection page for a course.
|
||||
* @param {string} courseKey Slash-separated course key.
|
||||
* @return {string} The URL to the track selection page.
|
||||
*/
|
||||
trackSelectionUrl: function( courseKey ) {
|
||||
return this.urls.trackSelection + courseKey + '/';
|
||||
},
|
||||
|
||||
/**
|
||||
* Construct a URL to enroll in a course.
|
||||
* @param {string} courseKey Slash-separated course key.
|
||||
* @return {string} The URL to enroll in a course.
|
||||
*/
|
||||
courseEnrollmentUrl: function( courseKey ) {
|
||||
return this.urls.course + courseKey;
|
||||
},
|
||||
|
||||
/**
|
||||
* Redirect to a URL. Mainly useful for mocking out in tests.
|
||||
* @param {string} url The URL to redirect to.
|
||||
*/
|
||||
redirect: function(url) {
|
||||
window.location.href = url;
|
||||
}
|
||||
};
|
||||
})(jQuery);
|
||||
@@ -1,87 +0,0 @@
|
||||
var edx = edx || {};
|
||||
|
||||
(function($, _, gettext) {
|
||||
'use strict';
|
||||
|
||||
edx.student = edx.student || {};
|
||||
edx.student.account = edx.student.account || {};
|
||||
|
||||
edx.student.account.EnrollmentInterface = {
|
||||
courseUrl: '/enrollment/v0/course/',
|
||||
|
||||
studentUrl: '/enrollment/v0/student',
|
||||
|
||||
trackSelectionUrl: '/course_modes/choose/',
|
||||
|
||||
headers: {
|
||||
'X-CSRFToken': $.cookie('csrftoken')
|
||||
},
|
||||
|
||||
studentInformation: function(courseKey) {
|
||||
// retrieve student enrollment information
|
||||
},
|
||||
|
||||
courseInformation: function(courseKey) {
|
||||
// retrieve course information from the enrollment API
|
||||
},
|
||||
|
||||
modeInArray: function(modeObjects, targetMode) {
|
||||
// Check if a given course mode slug exists in an array of mode objects
|
||||
var result = _.find(modeObjects, function(mode) {
|
||||
return mode.slug === targetMode;
|
||||
});
|
||||
|
||||
/* _.find returns the first value which passes the provided truth test,
|
||||
/* or undefined if no values pass the test
|
||||
*/
|
||||
return !_.isUndefined(result);
|
||||
},
|
||||
|
||||
enroll: function(courseKey, forwardUrl){
|
||||
var me = this;
|
||||
// attempt to enroll a student in a course
|
||||
$.ajax({
|
||||
url: this.courseUrl + courseKey,
|
||||
type: 'POST',
|
||||
data: {},
|
||||
headers: this.headers
|
||||
}).done(function(data){
|
||||
me.postEnrollmentHandler(courseKey, data, forwardUrl);
|
||||
}
|
||||
).fail(function(data, textStatus) {
|
||||
me.enrollmentFailureHandler(courseKey, data, forwardUrl);
|
||||
});
|
||||
},
|
||||
|
||||
enrollmentFailureHandler: function(courseKey, data, forwardUrl) {
|
||||
// handle failures to enroll via the API
|
||||
if(data.status == 400) {
|
||||
/* This status code probably means we don't have permissions to register
|
||||
/* for this course; look at the contents of the response
|
||||
*/
|
||||
var course = $.parseJSON(data.responseText);
|
||||
// see if it's a professional ed course
|
||||
if( 'course_modes' in course && this.modeInArray(course.course_modes, 'professional') ) {
|
||||
// forward appropriately
|
||||
forwardUrl = this.trackSelectionUrl + courseKey;
|
||||
}
|
||||
}
|
||||
// TODO: if we have a paid registration mode, add item to the cart and send them along
|
||||
|
||||
// TODO: we should figure out how to handle errors here
|
||||
window.location.href = forwardUrl;
|
||||
},
|
||||
|
||||
postEnrollmentHandler: function(courseKey, data, forwardUrl) {
|
||||
// Determine whether or not the course needs to be redirected to
|
||||
// a particular page.
|
||||
var course = data.course,
|
||||
course_modes = course.course_modes;
|
||||
|
||||
// send the user to the track selection page, because it will do the right thing
|
||||
forwardUrl = this.trackSelectionUrl + courseKey;
|
||||
|
||||
window.location.href = forwardUrl;
|
||||
}
|
||||
};
|
||||
})(jQuery, _, gettext);
|
||||
@@ -18,9 +18,9 @@ var edx = edx || {};
|
||||
|
||||
urlRoot: '',
|
||||
|
||||
initialize: function( obj ) {
|
||||
this.ajaxType = obj.method;
|
||||
this.urlRoot = obj.url;
|
||||
initialize: function( attributes, options ) {
|
||||
this.ajaxType = options.method;
|
||||
this.urlRoot = options.url;
|
||||
},
|
||||
|
||||
sync: function(method, model) {
|
||||
|
||||
@@ -16,9 +16,9 @@ var edx = edx || {};
|
||||
|
||||
urlRoot: '',
|
||||
|
||||
initialize: function( obj ) {
|
||||
this.ajaxType = obj.method;
|
||||
this.urlRoot = obj.url;
|
||||
initialize: function( attributes, options ) {
|
||||
this.ajaxType = options.method;
|
||||
this.urlRoot = options.url;
|
||||
},
|
||||
|
||||
sync: function(method, model) {
|
||||
|
||||
@@ -18,16 +18,15 @@ var edx = edx || {};
|
||||
year_of_birth: '',
|
||||
mailing_address: '',
|
||||
goals: '',
|
||||
honor_code: false
|
||||
},
|
||||
|
||||
ajaxType: '',
|
||||
|
||||
urlRoot: '',
|
||||
|
||||
initialize: function( obj ) {
|
||||
this.ajaxType = obj.method;
|
||||
this.urlRoot = obj.url;
|
||||
initialize: function( attributes, options ) {
|
||||
this.ajaxType = options.method;
|
||||
this.urlRoot = options.url;
|
||||
},
|
||||
|
||||
sync: function(method, model) {
|
||||
|
||||
49
lms/static/js/student_account/shoppingcart.js
Normal file
49
lms/static/js/student_account/shoppingcart.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Use the shopping cart to purchase courses.
|
||||
*/
|
||||
|
||||
var edx = edx || {};
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
edx.student = edx.student || {};
|
||||
edx.student.account = edx.student.account || {};
|
||||
|
||||
edx.student.account.ShoppingCartInterface = {
|
||||
|
||||
urls: {
|
||||
viewCart: "/shoppingcart/",
|
||||
addCourse: "/shoppingcart/add/course/"
|
||||
},
|
||||
|
||||
headers: {
|
||||
'X-CSRFToken': $.cookie('csrftoken')
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a course to a cart, then redirect to the view cart page.
|
||||
* @param {string} courseId The slash-separated course ID to add to the cart.
|
||||
*/
|
||||
addCourseToCart: function( courseId ) {
|
||||
$.ajax({
|
||||
url: this.urls.addCourse + courseId + "/",
|
||||
type: 'POST',
|
||||
data: {},
|
||||
headers: this.headers,
|
||||
context: this
|
||||
}).always(function() {
|
||||
this.redirect( this.urls.viewCart );
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Redirect to a URL. Mainly useful for mocking out in tests.
|
||||
* @param {string} url The URL to redirect to.
|
||||
*/
|
||||
redirect: function( url ) {
|
||||
window.location.href = url;
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
@@ -64,7 +64,7 @@ var edx = edx || {};
|
||||
|
||||
load: {
|
||||
login: function( data, context ) {
|
||||
var model = new edx.student.account.LoginModel({
|
||||
var model = new edx.student.account.LoginModel({}, {
|
||||
method: data.method,
|
||||
url: data.submit_url
|
||||
});
|
||||
@@ -78,10 +78,14 @@ var edx = edx || {};
|
||||
|
||||
// Listen for 'password-help' event to toggle sub-views
|
||||
context.listenTo( context.subview.login, 'password-help', context.resetPassword );
|
||||
|
||||
// Listen for 'auth-complete' event so we can enroll/redirect the user appropriately.
|
||||
context.listenTo( context.subview.login, 'auth-complete', context.authComplete );
|
||||
|
||||
},
|
||||
|
||||
reset: function( data, context ) {
|
||||
var model = new edx.student.account.PasswordResetModel({
|
||||
var model = new edx.student.account.PasswordResetModel({}, {
|
||||
method: data.method,
|
||||
url: data.submit_url
|
||||
});
|
||||
@@ -93,7 +97,7 @@ var edx = edx || {};
|
||||
},
|
||||
|
||||
register: function( data, context ) {
|
||||
var model = new edx.student.account.RegisterModel({
|
||||
var model = new edx.student.account.RegisterModel({}, {
|
||||
method: data.method,
|
||||
url: data.submit_url
|
||||
});
|
||||
@@ -104,6 +108,9 @@ var edx = edx || {};
|
||||
thirdPartyAuth: context.thirdPartyAuth,
|
||||
platformName: context.platformName
|
||||
});
|
||||
|
||||
// Listen for 'auth-complete' event so we can enroll/redirect the user appropriately.
|
||||
context.listenTo( context.subview.register, 'auth-complete', context.authComplete );
|
||||
}
|
||||
},
|
||||
|
||||
@@ -162,6 +169,92 @@ var edx = edx || {};
|
||||
},'slow');
|
||||
},
|
||||
|
||||
/**
|
||||
* Once authentication has completed successfully, a user may need to:
|
||||
*
|
||||
* - Enroll in a course.
|
||||
* - Add a course to the shopping cart.
|
||||
* - Be redirected to the dashboard / track selection page / shopping cart.
|
||||
*
|
||||
* This handler is triggered upon successful authentication,
|
||||
* either from the login or registration form. It checks
|
||||
* query string params, performs enrollment/shopping cart actions,
|
||||
* then redirects the user to the next page.
|
||||
*
|
||||
* The optional query string params are:
|
||||
*
|
||||
* ?next: If provided, redirect to this page upon successful auth.
|
||||
* Django uses this when an unauthenticated user accesses a view
|
||||
* decorated with @login_required.
|
||||
*
|
||||
* ?enrollment_action: Can be either "enroll" or "add_to_cart".
|
||||
* If you provide this param, you must also provide a `course_id` param;
|
||||
* otherwise, no action will be taken.
|
||||
*
|
||||
* ?course_id: The slash-separated course ID to enroll in or add to the cart.
|
||||
*
|
||||
*/
|
||||
authComplete: function() {
|
||||
var enrollment = edx.student.account.EnrollmentInterface,
|
||||
shoppingcart = edx.student.account.ShoppingCartInterface,
|
||||
redirectUrl = '/dashboard',
|
||||
queryParams = this.queryParams();
|
||||
|
||||
if ( queryParams.enrollmentAction === 'enroll' && queryParams.courseId) {
|
||||
/*
|
||||
If we need to enroll in a course, mark as enrolled.
|
||||
The enrollment interface will redirect the student once enrollment completes.
|
||||
*/
|
||||
enrollment.enroll( decodeURIComponent( queryParams.courseId ) );
|
||||
} else if ( queryParams.enrollmentAction === 'add_to_cart' && queryParams.courseId) {
|
||||
/*
|
||||
If this is a paid course, add it to the shopping cart and redirect
|
||||
the user to the "view cart" page.
|
||||
*/
|
||||
shoppingcart.addCourseToCart( decodeURIComponent( queryParams.courseId ) );
|
||||
} else {
|
||||
/*
|
||||
Otherwise, redirect the user to the next page
|
||||
Check for forwarding url and ensure that it isn't external.
|
||||
If not, use the default forwarding URL.
|
||||
*/
|
||||
if ( !_.isNull( queryParams.next ) ) {
|
||||
var next = decodeURIComponent( queryParams.next );
|
||||
|
||||
// Ensure that the URL is internal for security reasons
|
||||
if ( !window.isExternal( next ) ) {
|
||||
redirectUrl = next;
|
||||
}
|
||||
}
|
||||
|
||||
this.redirect( redirectUrl );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Redirect to a URL. Mainly useful for mocking out in tests.
|
||||
* @param {string} url The URL to redirect to.
|
||||
*/
|
||||
redirect: function( url ) {
|
||||
window.location.href = url;
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve query params that we use post-authentication
|
||||
* to decide whether to enroll a student in a course, add
|
||||
* an item to the cart, or redirect.
|
||||
*
|
||||
* @return {object} The query params. If any param is not
|
||||
* provided, it will default to null.
|
||||
*/
|
||||
queryParams: function() {
|
||||
return {
|
||||
next: $.url( '?next' ),
|
||||
enrollmentAction: $.url( '?enrollment_action' ),
|
||||
courseId: $.url( '?course_id' )
|
||||
};
|
||||
},
|
||||
|
||||
form: {
|
||||
isLoaded: function( $form ) {
|
||||
return $form.html().length > 0;
|
||||
|
||||
@@ -79,29 +79,7 @@ var edx = edx || {};
|
||||
},
|
||||
|
||||
saveSuccess: function () {
|
||||
var enrollment = edx.student.account.EnrollmentInterface,
|
||||
redirectUrl = '/dashboard',
|
||||
next = null;
|
||||
|
||||
// Check for forwarding url
|
||||
if ( !_.isNull( $.url('?next') ) ) {
|
||||
next = decodeURIComponent( $.url('?next') );
|
||||
|
||||
if ( !window.isExternal(next) ) {
|
||||
redirectUrl = next;
|
||||
}
|
||||
}
|
||||
|
||||
// If we need to enroll in a course, mark as enrolled
|
||||
if ( $.url('?enrollment_action') === 'enroll' ) {
|
||||
enrollment.enroll( decodeURIComponent( $.url('?course_id') ), redirectUrl );
|
||||
} else {
|
||||
this.redirect(redirectUrl);
|
||||
}
|
||||
},
|
||||
|
||||
redirect: function( url ) {
|
||||
window.location.href = url;
|
||||
this.trigger('auth-complete');
|
||||
},
|
||||
|
||||
saveError: function( error ) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var edx = edx || {};
|
||||
|
||||
(function($, _, gettext) {
|
||||
(function($, gettext) {
|
||||
'use strict';
|
||||
|
||||
edx.student = edx.student || {};
|
||||
@@ -39,4 +39,4 @@ var edx = edx || {};
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery, _, gettext);
|
||||
})(jQuery, gettext);
|
||||
|
||||
@@ -55,25 +55,7 @@ var edx = edx || {};
|
||||
},
|
||||
|
||||
saveSuccess: function() {
|
||||
var enrollment = edx.student.account.EnrollmentInterface,
|
||||
redirectUrl = '/dashboard',
|
||||
next = null;
|
||||
|
||||
// Check for forwarding url
|
||||
if ( !_.isNull( $.url('?next') ) ) {
|
||||
next = decodeURIComponent( $.url('?next') );
|
||||
|
||||
if ( !window.isExternal(next) ) {
|
||||
redirectUrl = next;
|
||||
}
|
||||
}
|
||||
|
||||
// If we need to enroll in a course, mark as enrolled
|
||||
if ( $.url('?enrollment_action') === 'enroll' ) {
|
||||
enrollment.enroll( decodeURIComponent( $.url('?course_id') ), redirectUrl );
|
||||
} else {
|
||||
this.redirect(redirectUrl);
|
||||
}
|
||||
this.trigger('auth-complete');
|
||||
},
|
||||
|
||||
redirect: function( url ) {
|
||||
|
||||
Reference in New Issue
Block a user