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
import mock
import ddt
import markupsafe
from django.test import TestCase
from django.conf import settings
from django.core.urlresolvers import reverse
......@@ -551,12 +552,16 @@ class StudentAccountLoginAndRegistrationTest(ModuleStoreTestCase):
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. """
expected_data = u"data-third-party-auth='{auth_info}'".format(
auth_info=json.dumps({
auth_info = markupsafe.escape(
json.dumps({
"currentProvider": current_provider,
"providers": providers
})
)
expected_data = u"data-third-party-auth='{auth_info}'".format(
auth_info=auth_info
)
self.assertContains(response, expected_data)
def _third_party_login_url(self, backend_name, auth_entry, course_id=None, redirect_url=None):
......
......@@ -7,7 +7,8 @@ from django.http import (
HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
)
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.utils.translation import ugettext as _
from django_future.csrf import ensure_csrf_cookie
......@@ -68,13 +69,24 @@ def login_and_registration_form(request, initial_mode="login"):
if request.user.is_authenticated():
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
context = {
'disable_courseware_js': True,
'initial_mode': initial_mode,
'third_party_auth': json.dumps(_third_party_auth_context(request)),
'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)
......@@ -317,3 +329,51 @@ def _third_party_auth_context(request):
context["currentProvider"] = current_provider.NAME
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([
var requests = 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 = {
method: 'post',
submit_url: '/submit',
......@@ -58,16 +44,6 @@ define([
FORWARD_URL = '/courseware/next',
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) {
// Spy on AJAX requests
requests = AjaxHelpers.requests(that);
......@@ -79,7 +55,10 @@ define([
currentProvider: null,
providers: []
},
platformName: 'edX'
platformName: 'edX',
loginFormDesc: FORM_DESCRIPTION,
registrationFormDesc: FORM_DESCRIPTION,
passwordResetFormDesc: FORM_DESCRIPTION
});
// Mock the redirect call
......@@ -88,9 +67,6 @@ define([
// 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);
};
var assertForms = function(visibleType, hiddenType) {
......@@ -106,8 +82,6 @@ define([
// Load form corresponding to the change event
view.toggleForm(changeEvent);
ajaxAssertAndRespond(AJAX_INFO[type].url, AJAX_INFO[type].requestIndex);
};
/**
......@@ -175,11 +149,6 @@ define([
// Simulate a click on the reset password link
view.resetPassword();
ajaxAssertAndRespond(
AJAX_INFO.password_reset.url,
AJAX_INFO.password_reset.requestIndex
);
// Verify that the password reset wrapper is populated
expect($('#password-reset-wrapper')).not.toBeEmpty();
});
......@@ -253,26 +222,6 @@ define([
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 || {};
edx.student = edx.student || {};
edx.student.account = edx.student.account || {};
var container = $('#login-and-registration-container');
return new edx.student.account.AccessView({
mode: $('#login-and-registration-container').data('initial-mode'),
thirdPartyAuth: $('#login-and-registration-container').data('third-party-auth'),
platformName: $('#login-and-registration-container').data('platform-name')
mode: container.data('initial-mode'),
thirdPartyAuth: container.data('third-party-auth'),
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);
......@@ -40,12 +40,20 @@ var edx = edx || {};
_.mixin( _s.exports() );
this.tpl = $(this.tpl).html();
this.activeForm = obj.mode || 'login';
this.thirdPartyAuth = obj.thirdPartyAuth || {
currentProvider: null,
providers: []
};
this.formDescriptions = {
login: obj.loginFormDesc,
register: obj.registrationFormDesc,
reset: obj.passwordResetFormDesc
};
this.platformName = obj.platformName;
// The login view listens for 'sync' events from the reset model
......@@ -73,82 +81,64 @@ var edx = edx || {};
},
loadForm: function( type ) {
this.getFormData( type, this );
var loadFunc = _.bind( this.load[type], this );
loadFunc( this.formDescriptions[type] );
},
load: {
login: function( data, context ) {
login: function( data ) {
var model = new edx.student.account.LoginModel({}, {
method: data.method,
url: data.submit_url
});
context.subview.login = new edx.student.account.LoginView({
this.subview.login = new edx.student.account.LoginView({
fields: data.fields,
model: model,
resetModel: context.resetModel,
thirdPartyAuth: context.thirdPartyAuth,
platformName: context.platformName
resetModel: this.resetModel,
thirdPartyAuth: this.thirdPartyAuth,
platformName: this.platformName
});
// 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.
context.listenTo( context.subview.login, 'auth-complete', context.authComplete );
this.listenTo( this.subview.login, 'auth-complete', this.authComplete );
},
reset: function( data, context ) {
context.resetModel.ajaxType = data.method;
context.resetModel.urlRoot = data.submit_url;
reset: function( data ) {
this.resetModel.ajaxType = data.method;
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,
model: context.resetModel
model: this.resetModel
});
// 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({}, {
method: data.method,
url: data.submit_url
});
context.subview.register = new edx.student.account.RegisterView({
this.subview.register = new edx.student.account.RegisterView({
fields: data.fields,
model: model,
thirdPartyAuth: context.thirdPartyAuth,
platformName: context.platformName
thirdPartyAuth: this.thirdPartyAuth,
platformName: this.platformName
});
// 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() {
this.element.hide( $(this.el).find('#password-reset-anchor') );
this.element.show( $('#login-anchor') );
......@@ -165,10 +155,6 @@ var edx = edx || {};
this.element.scrollTop( $('#password-reset-anchor') );
},
showFormError: function() {
this.element.show( $('#form-load-fail') );
},
toggleForm: function( e ) {
var type = $(e.currentTarget).data('type'),
$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">
<div id="login-form" class="form-wrapper <% if ( mode !== 'login' ) { %>hidden<% } %>"></div>
</section>
......
......@@ -26,8 +26,11 @@
<div id="login-and-registration-container"
class="login-register"
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-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>
......
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