Commit 284e78c2 by Will Daly

Merge pull request #6690 from edx/will/inline-user-api-forms

Inline the user api form description calls
parents 349a02e4 0a9f9130
...@@ -8,6 +8,7 @@ import json ...@@ -8,6 +8,7 @@ import json
import mock import mock
import ddt import ddt
import markupsafe
from django.test import TestCase from django.test import TestCase
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
...@@ -551,12 +552,16 @@ class StudentAccountLoginAndRegistrationTest(ModuleStoreTestCase): ...@@ -551,12 +552,16 @@ class StudentAccountLoginAndRegistrationTest(ModuleStoreTestCase):
def _assert_third_party_auth_data(self, response, current_provider, providers): def _assert_third_party_auth_data(self, response, current_provider, providers):
"""Verify that third party auth info is rendered correctly in a DOM data attribute. """ """Verify that third party auth info is rendered correctly in a DOM data attribute. """
expected_data = u"data-third-party-auth='{auth_info}'".format( auth_info = markupsafe.escape(
auth_info=json.dumps({ json.dumps({
"currentProvider": current_provider, "currentProvider": current_provider,
"providers": providers "providers": providers
}) })
) )
expected_data = u"data-third-party-auth='{auth_info}'".format(
auth_info=auth_info
)
self.assertContains(response, expected_data) self.assertContains(response, expected_data)
def _third_party_login_url(self, backend_name, auth_entry, course_id=None, redirect_url=None): def _third_party_login_url(self, backend_name, auth_entry, course_id=None, redirect_url=None):
......
...@@ -7,7 +7,8 @@ from django.http import ( ...@@ -7,7 +7,8 @@ from django.http import (
HttpResponse, HttpResponseBadRequest, HttpResponseForbidden HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
) )
from django.shortcuts import redirect from django.shortcuts import redirect
from django.core.urlresolvers import reverse from django.http import HttpRequest
from django.core.urlresolvers import reverse, resolve
from django.core.mail import send_mail from django.core.mail import send_mail
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django_future.csrf import ensure_csrf_cookie from django_future.csrf import ensure_csrf_cookie
...@@ -68,13 +69,24 @@ def login_and_registration_form(request, initial_mode="login"): ...@@ -68,13 +69,24 @@ def login_and_registration_form(request, initial_mode="login"):
if request.user.is_authenticated(): if request.user.is_authenticated():
return redirect(reverse('dashboard')) return redirect(reverse('dashboard'))
# Retrieve the form descriptions from the user API
form_descriptions = _get_form_descriptions(request)
# Otherwise, render the combined login/registration page # Otherwise, render the combined login/registration page
context = { context = {
'disable_courseware_js': True, 'disable_courseware_js': True,
'initial_mode': initial_mode, 'initial_mode': initial_mode,
'third_party_auth': json.dumps(_third_party_auth_context(request)), 'third_party_auth': json.dumps(_third_party_auth_context(request)),
'platform_name': settings.PLATFORM_NAME, 'platform_name': settings.PLATFORM_NAME,
'responsive': True 'responsive': True,
# Include form descriptions retrieved from the user API.
# We could have the JS client make these requests directly,
# but we include them in the initial page load to avoid
# the additional round-trip to the server.
'login_form_desc': form_descriptions['login'],
'registration_form_desc': form_descriptions['registration'],
'password_reset_form_desc': form_descriptions['password_reset'],
} }
return render_to_response('student_account/login_and_register.html', context) return render_to_response('student_account/login_and_register.html', context)
...@@ -317,3 +329,51 @@ def _third_party_auth_context(request): ...@@ -317,3 +329,51 @@ def _third_party_auth_context(request):
context["currentProvider"] = current_provider.NAME context["currentProvider"] = current_provider.NAME
return context return context
def _get_form_descriptions(request):
"""Retrieve form descriptions from the user API.
Arguments:
request (HttpRequest): The original request, used to retrieve session info.
Returns:
dict: Keys are 'login', 'registration', and 'password_reset';
values are the JSON-serialized form descriptions.
"""
return {
'login': _local_server_get('/user_api/v1/account/login_session/', request.session),
'registration': _local_server_get('/user_api/v1/account/registration/', request.session),
'password_reset': _local_server_get('/user_api/v1/account/password_reset/', request.session)
}
def _local_server_get(url, session):
"""Simulate a server-server GET request for an in-process API.
Arguments:
url (str): The URL of the request (excluding the protocol and domain)
session (SessionStore): The session of the original request,
used to get past the CSRF checks.
Returns:
str: The content of the response
"""
# Since the user API is currently run in-process,
# we simulate the server-server API call by constructing
# our own request object. We don't need to include much
# information in the request except for the session
# (to get past through CSRF validation)
request = HttpRequest()
request.method = "GET"
request.session = session
# Call the Django view function, simulating
# the server-server API call
view, args, kwargs = resolve(url)
response = view(request, *args, **kwargs)
# Return the content of the response
return response.content
...@@ -13,20 +13,6 @@ define([ ...@@ -13,20 +13,6 @@ define([
var requests = null, var requests = null,
view = null, view = null,
AJAX_INFO = {
register: {
url: '/user_api/v1/account/registration/',
requestIndex: 1
},
login: {
url: '/user_api/v1/account/login_session/',
requestIndex: 0
},
password_reset: {
url: '/user_api/v1/account/password_reset/',
requestIndex: 1
}
},
FORM_DESCRIPTION = { FORM_DESCRIPTION = {
method: 'post', method: 'post',
submit_url: '/submit', submit_url: '/submit',
...@@ -58,16 +44,6 @@ define([ ...@@ -58,16 +44,6 @@ define([
FORWARD_URL = '/courseware/next', FORWARD_URL = '/courseware/next',
COURSE_KEY = 'edx/DemoX/Fall'; COURSE_KEY = 'edx/DemoX/Fall';
var ajaxAssertAndRespond = function(url, requestIndex) {
// Verify that the client contacts the server as expected
AjaxHelpers.expectJsonRequest(requests, 'GET', url, null, requestIndex);
/* Simulate a response from the server containing
/* a dummy form description
*/
AjaxHelpers.respondWithJson(requests, FORM_DESCRIPTION);
};
var ajaxSpyAndInitialize = function(that, mode) { var ajaxSpyAndInitialize = function(that, mode) {
// Spy on AJAX requests // Spy on AJAX requests
requests = AjaxHelpers.requests(that); requests = AjaxHelpers.requests(that);
...@@ -79,7 +55,10 @@ define([ ...@@ -79,7 +55,10 @@ define([
currentProvider: null, currentProvider: null,
providers: [] providers: []
}, },
platformName: 'edX' platformName: 'edX',
loginFormDesc: FORM_DESCRIPTION,
registrationFormDesc: FORM_DESCRIPTION,
passwordResetFormDesc: FORM_DESCRIPTION
}); });
// Mock the redirect call // Mock the redirect call
...@@ -88,9 +67,6 @@ define([ ...@@ -88,9 +67,6 @@ define([
// Mock the enrollment and shopping cart interfaces // Mock the enrollment and shopping cart interfaces
spyOn( EnrollmentInterface, 'enroll' ).andCallFake( function() {} ); spyOn( EnrollmentInterface, 'enroll' ).andCallFake( function() {} );
spyOn( ShoppingCartInterface, 'addCourseToCart' ).andCallFake( function() {} ); spyOn( ShoppingCartInterface, 'addCourseToCart' ).andCallFake( function() {} );
// Initialize the subview
ajaxAssertAndRespond(AJAX_INFO[mode].url);
}; };
var assertForms = function(visibleType, hiddenType) { var assertForms = function(visibleType, hiddenType) {
...@@ -106,8 +82,6 @@ define([ ...@@ -106,8 +82,6 @@ define([
// Load form corresponding to the change event // Load form corresponding to the change event
view.toggleForm(changeEvent); view.toggleForm(changeEvent);
ajaxAssertAndRespond(AJAX_INFO[type].url, AJAX_INFO[type].requestIndex);
}; };
/** /**
...@@ -175,11 +149,6 @@ define([ ...@@ -175,11 +149,6 @@ define([
// Simulate a click on the reset password link // Simulate a click on the reset password link
view.resetPassword(); view.resetPassword();
ajaxAssertAndRespond(
AJAX_INFO.password_reset.url,
AJAX_INFO.password_reset.requestIndex
);
// Verify that the password reset wrapper is populated // Verify that the password reset wrapper is populated
expect($('#password-reset-wrapper')).not.toBeEmpty(); expect($('#password-reset-wrapper')).not.toBeEmpty();
}); });
...@@ -253,26 +222,6 @@ define([ ...@@ -253,26 +222,6 @@ define([
expect( view.redirect ).toHaveBeenCalledWith( "/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);
// Init AccessView
view = new AccessView({
mode: 'login',
thirdPartyAuth: {
currentProvider: null,
providers: []
},
platformName: 'edX'
});
// Simulate an error from the LMS servers
AjaxHelpers.respondWithError(requests);
// Error message should be displayed
expect( $('#form-load-fail').hasClass('hidden') ).toBe(false);
});
}); });
} }
); );
...@@ -6,9 +6,14 @@ var edx = edx || {}; ...@@ -6,9 +6,14 @@ var edx = edx || {};
edx.student = edx.student || {}; edx.student = edx.student || {};
edx.student.account = edx.student.account || {}; edx.student.account = edx.student.account || {};
var container = $('#login-and-registration-container');
return new edx.student.account.AccessView({ return new edx.student.account.AccessView({
mode: $('#login-and-registration-container').data('initial-mode'), mode: container.data('initial-mode'),
thirdPartyAuth: $('#login-and-registration-container').data('third-party-auth'), thirdPartyAuth: container.data('third-party-auth'),
platformName: $('#login-and-registration-container').data('platform-name') platformName: container.data('platform-name'),
loginFormDesc: container.data('login-form-desc'),
registrationFormDesc: container.data('registration-form-desc'),
passwordResetFormDesc: container.data('password-reset-form-desc')
}); });
})(jQuery); })(jQuery);
...@@ -40,12 +40,20 @@ var edx = edx || {}; ...@@ -40,12 +40,20 @@ var edx = edx || {};
_.mixin( _s.exports() ); _.mixin( _s.exports() );
this.tpl = $(this.tpl).html(); this.tpl = $(this.tpl).html();
this.activeForm = obj.mode || 'login'; this.activeForm = obj.mode || 'login';
this.thirdPartyAuth = obj.thirdPartyAuth || { this.thirdPartyAuth = obj.thirdPartyAuth || {
currentProvider: null, currentProvider: null,
providers: [] providers: []
}; };
this.formDescriptions = {
login: obj.loginFormDesc,
register: obj.registrationFormDesc,
reset: obj.passwordResetFormDesc
};
this.platformName = obj.platformName; this.platformName = obj.platformName;
// The login view listens for 'sync' events from the reset model // The login view listens for 'sync' events from the reset model
...@@ -73,82 +81,64 @@ var edx = edx || {}; ...@@ -73,82 +81,64 @@ var edx = edx || {};
}, },
loadForm: function( type ) { loadForm: function( type ) {
this.getFormData( type, this ); var loadFunc = _.bind( this.load[type], this );
loadFunc( this.formDescriptions[type] );
}, },
load: { load: {
login: function( data, context ) { login: function( data ) {
var model = new edx.student.account.LoginModel({}, { var model = new edx.student.account.LoginModel({}, {
method: data.method, method: data.method,
url: data.submit_url url: data.submit_url
}); });
context.subview.login = new edx.student.account.LoginView({ this.subview.login = new edx.student.account.LoginView({
fields: data.fields, fields: data.fields,
model: model, model: model,
resetModel: context.resetModel, resetModel: this.resetModel,
thirdPartyAuth: context.thirdPartyAuth, thirdPartyAuth: this.thirdPartyAuth,
platformName: context.platformName platformName: this.platformName
}); });
// Listen for 'password-help' event to toggle sub-views // Listen for 'password-help' event to toggle sub-views
context.listenTo( context.subview.login, 'password-help', context.resetPassword ); this.listenTo( this.subview.login, 'password-help', this.resetPassword );
// Listen for 'auth-complete' event so we can enroll/redirect the user appropriately. // Listen for 'auth-complete' event so we can enroll/redirect the user appropriately.
context.listenTo( context.subview.login, 'auth-complete', context.authComplete ); this.listenTo( this.subview.login, 'auth-complete', this.authComplete );
}, },
reset: function( data, context ) { reset: function( data ) {
context.resetModel.ajaxType = data.method; this.resetModel.ajaxType = data.method;
context.resetModel.urlRoot = data.submit_url; this.resetModel.urlRoot = data.submit_url;
context.subview.passwordHelp = new edx.student.account.PasswordResetView({ this.subview.passwordHelp = new edx.student.account.PasswordResetView({
fields: data.fields, fields: data.fields,
model: context.resetModel model: this.resetModel
}); });
// Listen for 'password-email-sent' event to toggle sub-views // Listen for 'password-email-sent' event to toggle sub-views
context.listenTo( context.subview.passwordHelp, 'password-email-sent', context.passwordEmailSent ); this.listenTo( this.subview.passwordHelp, 'password-email-sent', this.passwordEmailSent );
}, },
register: function( data, context ) { register: function( data ) {
var model = new edx.student.account.RegisterModel({}, { var model = new edx.student.account.RegisterModel({}, {
method: data.method, method: data.method,
url: data.submit_url url: data.submit_url
}); });
context.subview.register = new edx.student.account.RegisterView({ this.subview.register = new edx.student.account.RegisterView({
fields: data.fields, fields: data.fields,
model: model, model: model,
thirdPartyAuth: context.thirdPartyAuth, thirdPartyAuth: this.thirdPartyAuth,
platformName: context.platformName platformName: this.platformName
}); });
// Listen for 'auth-complete' event so we can enroll/redirect the user appropriately. // Listen for 'auth-complete' event so we can enroll/redirect the user appropriately.
context.listenTo( context.subview.register, 'auth-complete', context.authComplete ); this.listenTo( this.subview.register, 'auth-complete', this.authComplete );
} }
}, },
getFormData: function( type, context ) {
var urls = {
login: 'login_session',
register: 'registration',
reset: 'password_reset'
};
$.ajax({
url: '/user_api/v1/account/' + urls[type] + '/',
type: 'GET',
dataType: 'json',
context: this,
success: function( data ) {
this.load[type]( data, context );
},
error: this.showFormError
});
},
passwordEmailSent: function() { passwordEmailSent: function() {
this.element.hide( $(this.el).find('#password-reset-anchor') ); this.element.hide( $(this.el).find('#password-reset-anchor') );
this.element.show( $('#login-anchor') ); this.element.show( $('#login-anchor') );
...@@ -165,10 +155,6 @@ var edx = edx || {}; ...@@ -165,10 +155,6 @@ var edx = edx || {};
this.element.scrollTop( $('#password-reset-anchor') ); this.element.scrollTop( $('#password-reset-anchor') );
}, },
showFormError: function() {
this.element.show( $('#form-load-fail') );
},
toggleForm: function( e ) { toggleForm: function( e ) {
var type = $(e.currentTarget).data('type'), var type = $(e.currentTarget).data('type'),
$form = $('#' + type + '-form'), $form = $('#' + type + '-form'),
......
<section id="form-load-fail" class="form-type hidden">
<div class="status submission-error">
<p class="message-copy"><%- gettext("Sorry, we're having some technical problems. Wait a few minutes and try again.") %></p>
</div>
</section>
<section id="login-anchor" class="form-type"> <section id="login-anchor" class="form-type">
<div id="login-form" class="form-wrapper <% if ( mode !== 'login' ) { %>hidden<% } %>"></div> <div id="login-form" class="form-wrapper <% if ( mode !== 'login' ) { %>hidden<% } %>"></div>
</section> </section>
......
...@@ -26,8 +26,11 @@ ...@@ -26,8 +26,11 @@
<div id="login-and-registration-container" <div id="login-and-registration-container"
class="login-register" class="login-register"
data-initial-mode="${initial_mode}" data-initial-mode="${initial_mode}"
data-third-party-auth='${third_party_auth}' data-third-party-auth='${third_party_auth|h}'
data-platform-name='${platform_name}' data-platform-name='${platform_name}'
data-login-form-desc='${login_form_desc|h}'
data-registration-form-desc='${registration_form_desc|h}'
data-password-reset-form-desc='${password_reset_form_desc|h}'
/> />
</div> </div>
......
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