Commit 4c51f873 by Will Daly

Merge pull request #5883 from edx/will/logistration-enrollment-cleanup

Logistration: enrollment cleanup and a few fixes
parents 1a5cc867 e629ce2b
......@@ -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);
......
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);
});
});
}
);
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);
});
});
}
......
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 );
});
});
}
);
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);
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) {
......
/**
* 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 ) {
......
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 ) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment