Commit dae0285a by Zubair Afzal

Merge pull request #9473 from edx/zub/story/ecom-2044-login-registration-requirejs

use the standard syntax to load JavaScript dependencies on logistrati…
parents bd3dfee9 d2a47908
......@@ -69,8 +69,8 @@ class IntegrationTestLTI(testutil.TestCase):
self.assertTrue(login_response['Location'].endswith(reverse('signin_user')))
register_response = self.client.get(login_response['Location'])
self.assertEqual(register_response.status_code, 200)
self.assertIn('currentProvider": "LTI Test Tool Consumer"', register_response.content)
self.assertIn('"errorMessage": null', register_response.content)
self.assertIn('"currentProvider": "LTI Test Tool Consumer"', register_response.content)
self.assertIn('"errorMessage": null', register_response.content)
# Now complete the form:
ajax_register_response = self.client.post(
......@@ -153,7 +153,7 @@ class IntegrationTestLTI(testutil.TestCase):
register_response = self.client.get(login_response['Location'])
self.assertEqual(register_response.status_code, 200)
self.assertIn(
'currentProvider": "Tool Consumer with Secret in Settings"',
'"currentProvider": "Tool Consumer with Secret in Settings"',
register_response.content
)
self.assertIn('"errorMessage": null', register_response.content)
self.assertIn('"errorMessage": null', register_response.content)
"""
Third_party_auth integration tests using a mock version of the TestShib provider
"""
from django.core.urlresolvers import reverse
import json
import unittest
import httpretty
from mock import patch
from django.core.urlresolvers import reverse
from openedx.core.lib.json_utils import EscapedEdxJSONEncoder
from student.tests.factories import UserFactory
from third_party_auth.tasks import fetch_saml_metadata
from third_party_auth.tests import testutil
import unittest
TESTSHIB_ENTITY_ID = 'https://idp.testshib.org/idp/shibboleth'
TESTSHIB_METADATA_URL = 'https://mock.testshib.org/metadata/testshib-providers.xml'
......@@ -81,11 +88,11 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
# We'd now like to see if the "You've successfully signed into TestShib" message is
# shown, but it's managed by a JavaScript runtime template, and we can't run JS in this
# type of test, so we just check for the variable that triggers that message.
self.assertIn('"currentProvider": "TestShib"', register_response.content)
self.assertIn('"errorMessage": null', register_response.content)
self.assertIn('"currentProvider": "TestShib"', register_response.content)
self.assertIn('"errorMessage": null', register_response.content)
# Now do a crude check that the data (e.g. email) from the provider is displayed in the form:
self.assertIn('"defaultValue": "myself@testshib.org"', register_response.content)
self.assertIn('"defaultValue": "Me Myself And I"', register_response.content)
self.assertIn('"defaultValue": "myself@testshib.org"', register_response.content)
self.assertIn('"defaultValue": "Me Myself And I"', register_response.content)
# Now complete the form:
ajax_register_response = self.client.post(
reverse('user_api_registration'),
......@@ -128,8 +135,8 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
# We'd now like to see if the "You've successfully signed into TestShib" message is
# shown, but it's managed by a JavaScript runtime template, and we can't run JS in this
# type of test, so we just check for the variable that triggers that message.
self.assertIn('"currentProvider": "TestShib"', login_response.content)
self.assertIn('"errorMessage": null', login_response.content)
self.assertIn('"currentProvider": "TestShib"', login_response.content)
self.assertIn('"errorMessage": null', login_response.content)
# Now the user enters their username and password.
# The AJAX on the page will log them in:
ajax_login_response = self.client.post(
......@@ -183,7 +190,7 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
response = self.client.get(self.login_page_url)
self.assertEqual(response.status_code, 200)
self.assertIn("TestShib", response.content)
self.assertIn(TPA_TESTSHIB_LOGIN_URL.replace('&', '&'), response.content)
self.assertIn(json.dumps(TPA_TESTSHIB_LOGIN_URL, cls=EscapedEdxJSONEncoder), response.content)
return response
def _check_register_page(self):
......@@ -191,7 +198,7 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
response = self.client.get(self.register_page_url)
self.assertEqual(response.status_code, 200)
self.assertIn("TestShib", response.content)
self.assertIn(TPA_TESTSHIB_REGISTER_URL.replace('&', '&'), response.content)
self.assertIn(json.dumps(TPA_TESTSHIB_REGISTER_URL, cls=EscapedEdxJSONEncoder), response.content)
return response
def _configure_testshib_provider(self, **kwargs):
......
describe('edx.utils.validate', function () {
;(function (define) {
'use strict';
define(['jquery', 'js/utils/edx.utils.validate'],
function($) {
var fixture = null,
field = null,
......@@ -189,4 +191,5 @@ describe('edx.utils.validate', function () {
expectInvalid(CUSTOM_MESSAGE);
});
});
});
}).call(this, define || RequireJS.define);
var edx = edx || {};
(function( $, _, _s, gettext ) {
;(function (define) {
'use strict';
define([
'jquery',
'underscore',
'underscore.string',
'gettext'
],
function($, _, _s, gettext) {
var utils;
/* Mix non-conflicting functions from underscore.string
* (all but include, contains, and reverse) into the
......@@ -9,11 +15,12 @@ var edx = edx || {};
* by the access view, but doing it here helps keep the
* utility self-contained.
*/
_.mixin( _.str.exports() );
edx.utils = edx.utils || {};
if (_.isUndefined(_s)) {
_s = _.str;
}
_.mixin( _s.exports() );
var utils = (function(){
utils = (function(){
var _fn = {
validate: {
......@@ -181,6 +188,6 @@ var edx = edx || {};
})();
edx.utils.validate = utils.validate;
})( jQuery, _, _.str, gettext );
return utils;
});
}).call(this, define || RequireJS.define);
......@@ -8,7 +8,6 @@ import json
import mock
import ddt
import markupsafe
from django.conf import settings
from django.core.urlresolvers import reverse
from django.core import mail
......@@ -20,6 +19,7 @@ from django.test.client import RequestFactory
from openedx.core.djangoapps.user_api.accounts.api import activate_account, create_account
from openedx.core.djangoapps.user_api.accounts import EMAIL_MAX_LENGTH
from openedx.core.lib.json_utils import EscapedEdxJSONEncoder
from student.tests.factories import UserFactory
from student_account.views import account_settings_context
from third_party_auth.tests.testutil import simulate_running_pipeline, ThirdPartyAuthTestMixin
......@@ -223,7 +223,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
@ddt.unpack
def test_login_and_registration_form(self, url_name, initial_mode):
response = self.client.get(reverse(url_name))
expected_data = u"data-initial-mode=\"{mode}\"".format(mode=initial_mode)
expected_data = '"initial_mode": "{mode}"'.format(mode=initial_mode)
self.assertContains(response, expected_data)
@ddt.data("signin_user", "register_user")
......@@ -255,6 +255,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
# that preserves the querystring params
with mock.patch.dict(settings.FEATURES, {'IS_EDX_DOMAIN': is_edx_domain}):
response = self.client.get(reverse(url_name), params)
expected_url = '/login?{}'.format(self._finish_auth_url_param(params + [('next', '/dashboard')]))
self.assertContains(response, expected_url)
......@@ -330,7 +331,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
def test_hinted_login(self):
params = [("next", "/courses/something/?tpa_hint=oa2-google-oauth2")]
response = self.client.get(reverse('signin_user'), params)
self.assertContains(response, "data-third-party-auth-hint='oa2-google-oauth2'")
self.assertContains(response, '"third_party_auth_hint": "oa2-google-oauth2"')
@override_settings(SITE_NAME=settings.MICROSITE_TEST_HOSTNAME)
def test_microsite_uses_old_login_page(self):
......@@ -358,17 +359,17 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
finish_auth_url = None
if current_backend:
finish_auth_url = reverse("social:complete", kwargs={"backend": current_backend}) + "?"
auth_info = markupsafe.escape(
json.dumps({
auth_info = {
"currentProvider": current_provider,
"providers": providers,
"secondaryProviders": [],
"finishAuthUrl": finish_auth_url,
"errorMessage": None,
})
)
}
auth_info = json.dumps(auth_info, cls=EscapedEdxJSONEncoder)
expected_data = u"data-third-party-auth='{auth_info}'".format(
expected_data = '"third_party_auth": {auth_info}'.format(
auth_info=auth_info
)
......
......@@ -95,22 +95,25 @@ def login_and_registration_form(request, initial_mode="login"):
# Otherwise, render the combined login/registration page
context = {
'login_redirect_url': redirect_to, # This gets added to the query string of the "Sign In" button in the header
'disable_courseware_js': True,
'data': {
'login_redirect_url': redirect_to,
'initial_mode': initial_mode,
'third_party_auth': json.dumps(_third_party_auth_context(request, redirect_to)),
'third_party_auth': _third_party_auth_context(request, redirect_to),
'third_party_auth_hint': third_party_auth_hint or '',
'platform_name': settings.PLATFORM_NAME,
'responsive': True,
'allow_iframing': 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'],
'login_form_desc': json.loads(form_descriptions['login']),
'registration_form_desc': json.loads(form_descriptions['registration']),
'password_reset_form_desc': json.loads(form_descriptions['password_reset']),
},
'login_redirect_url': redirect_to, # This gets added to the query string of the "Sign In" button in header
'responsive': True,
'allow_iframing': True,
'disable_courseware_js': True,
}
return render_to_response('student_account/login_and_register.html', context)
......
......@@ -1303,26 +1303,6 @@ instructor_dash_js = (
sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/instructor_dashboard/**/*.js'))
)
# JavaScript used by the student account and profile pages
# These are not courseware, so they do not need many of the courseware-specific
# JavaScript modules.
student_account_js = [
'js/utils/edx.utils.validate.js',
'js/sticky_filter.js',
'js/query-params.js',
'js/student_account/models/LoginModel.js',
'js/student_account/models/RegisterModel.js',
'js/student_account/models/PasswordResetModel.js',
'js/student_account/views/FormView.js',
'js/student_account/views/LoginView.js',
'js/student_account/views/HintedLoginView.js',
'js/student_account/views/RegisterView.js',
'js/student_account/views/PasswordResetView.js',
'js/student_account/views/AccessView.js',
'js/student_account/views/InstitutionLoginView.js',
'js/student_account/accessApp.js',
]
verify_student_js = [
'js/sticky_filter.js',
'js/query-params.js',
......@@ -1574,10 +1554,6 @@ PIPELINE_JS = {
'source_filenames': dashboard_js,
'output_filename': 'js/dashboard.js'
},
'student_account': {
'source_filenames': student_account_js,
'output_filename': 'js/student_account.js'
},
'verify_student': {
'source_filenames': verify_student_js,
'output_filename': 'js/verify_student.js'
......
......@@ -75,16 +75,6 @@
'js/views/file_uploader': 'js/views/file_uploader',
'js/views/notification': 'js/views/notification',
'js/student_account/account': 'js/student_account/account',
'js/student_account/views/FormView': 'js/student_account/views/FormView',
'js/student_account/models/LoginModel': 'js/student_account/models/LoginModel',
'js/student_account/views/LoginView': 'js/student_account/views/LoginView',
'js/student_account/views/InstitutionLoginView': 'js/student_account/views/InstitutionLoginView',
'js/student_account/models/PasswordResetModel': 'js/student_account/models/PasswordResetModel',
'js/student_account/views/PasswordResetView': 'js/student_account/views/PasswordResetView',
'js/student_account/models/RegisterModel': 'js/student_account/models/RegisterModel',
'js/student_account/views/RegisterView': 'js/student_account/views/RegisterView',
'js/student_account/views/AccessView': 'js/student_account/views/AccessView',
'js/student_account/views/HintedLoginView': 'js/student_account/views/HintedLoginView',
'js/student_profile/views/learner_profile_fields': 'js/student_profile/views/learner_profile_fields',
'js/student_profile/views/learner_profile_factory': 'js/student_profile/views/learner_profile_factory',
'js/student_profile/views/learner_profile_view': 'js/student_profile/views/learner_profile_view',
......@@ -94,7 +84,10 @@
'DiscussionModuleView': 'xmodule_js/common_static/coffee/src/discussion/discussion_module_view',
// edxnotes
'annotator_1.2.9': 'xmodule_js/common_static/js/vendor/edxnotes/annotator-full.min'
'annotator_1.2.9': 'xmodule_js/common_static/js/vendor/edxnotes/annotator-full.min',
// Common edx utils
'js/utils/edx.utils.validate': 'xmodule_js/common_static/js/utils/edx.utils.validate'
},
shim: {
'gettext': {
......@@ -337,91 +330,6 @@
'js/models/notification', 'jquery.fileupload'
]
},
// Student account registration/login
// Loaded explicitly until these are converted to RequireJS
'js/student_account/views/FormView': {
exports: 'edx.student.account.FormView',
deps: ['jquery', 'underscore', 'backbone', 'gettext']
},
'js/student_account/models/LoginModel': {
exports: 'edx.student.account.LoginModel',
deps: ['jquery', 'jquery.cookie', 'backbone']
},
'js/student_account/views/LoginView': {
exports: 'edx.student.account.LoginView',
deps: [
'jquery',
'jquery.url',
'underscore',
'gettext',
'js/student_account/models/LoginModel',
'js/student_account/views/FormView'
]
},
'js/student_account/views/InstitutionLoginView': {
exports: 'edx.student.account.InstitutionLoginView',
deps: [
'jquery',
'underscore',
'backbone'
]
},
'js/student_account/models/PasswordResetModel': {
exports: 'edx.student.account.PasswordResetModel',
deps: ['jquery', 'jquery.cookie', 'backbone']
},
'js/student_account/views/PasswordResetView': {
exports: 'edx.student.account.PasswordResetView',
deps: [
'jquery',
'underscore',
'gettext',
'js/student_account/models/PasswordResetModel',
'js/student_account/views/FormView'
]
},
'js/student_account/models/RegisterModel': {
exports: 'edx.student.account.RegisterModel',
deps: ['jquery', 'jquery.cookie', 'backbone']
},
'js/student_account/views/RegisterView': {
exports: 'edx.student.account.RegisterView',
deps: [
'jquery',
'jquery.url',
'underscore',
'gettext',
'js/student_account/models/RegisterModel',
'js/student_account/views/FormView'
]
},
'js/student_account/views/HintedLoginView': {
exports: 'edx.student.account.HintedLoginView',
deps: [
'jquery',
'underscore',
'backbone',
'gettext'
]
},
'js/student_account/views/AccessView': {
exports: 'edx.student.account.AccessView',
deps: [
'jquery',
'underscore',
'backbone',
'history',
'utility',
'js/student_account/views/LoginView',
'js/student_account/views/PasswordResetView',
'js/student_account/views/RegisterView',
'js/student_account/views/InstitutionLoginView',
'js/student_account/models/LoginModel',
'js/student_account/models/PasswordResetModel',
'js/student_account/models/RegisterModel',
'js/student_account/views/FormView'
]
},
'js/verify_student/models/verification_model': {
exports: 'edx.verify_student.VerificationModel',
deps: [ 'jquery', 'underscore', 'backbone', 'jquery.cookie' ]
......@@ -731,6 +639,7 @@
'lms/include/js/spec/instructor_dashboard/student_admin_spec.js',
'lms/include/js/spec/student_account/account_spec.js',
'lms/include/js/spec/student_account/access_spec.js',
'lms/include/js/spec/student_account/logistration_factory_spec.js',
'lms/include/js/spec/student_account/finish_auth_spec.js',
'lms/include/js/spec/student_account/hinted_login_spec.js',
'lms/include/js/spec/student_account/login_spec.js',
......
define([
;(function (define) {
'use strict';
define([
'jquery',
'underscore',
'backbone',
'common/js/spec_helpers/template_helpers',
'common/js/spec_helpers/ajax_helpers',
'js/student_account/views/AccessView',
......@@ -7,8 +11,10 @@ define([
'js/student_account/enrollment',
'js/student_account/shoppingcart',
'js/student_account/emailoptin'
], function($, TemplateHelpers, AjaxHelpers, AccessView, FormView, EnrollmentInterface, ShoppingCartInterface) {
"use strict";
],
function($, _, Backbone, TemplateHelpers, AjaxHelpers, AccessView, FormView, EnrollmentInterface,
ShoppingCartInterface) {
describe('edx.student.account.AccessView', function() {
var requests = null,
view = null,
......@@ -24,7 +30,7 @@ define([
required: true,
placeholder: 'xsy@edx.org',
instructions: 'Enter your email here.',
restrictions: {},
restrictions: {}
},
{
name: 'username',
......@@ -49,24 +55,27 @@ define([
THIRD_PARTY_COMPLETE_URL = '/auth/complete/provider/';
var ajaxSpyAndInitialize = function(that, mode, nextUrl, finishAuthUrl) {
// Spy on AJAX requests
requests = AjaxHelpers.requests(that);
// Initialize the access view
view = new AccessView({
mode: mode,
thirdPartyAuth: {
var options = {
initial_mode: mode,
third_party_auth: {
currentProvider: null,
providers: [],
secondaryProviders: [{name: "provider"}],
finishAuthUrl: finishAuthUrl
},
nextUrl: nextUrl, // undefined for default
platformName: 'edX',
loginFormDesc: FORM_DESCRIPTION,
registrationFormDesc: FORM_DESCRIPTION,
passwordResetFormDesc: FORM_DESCRIPTION
});
login_redirect_url: nextUrl, // undefined for default
platform_name: 'edX',
login_form_desc: FORM_DESCRIPTION,
registration_form_desc: FORM_DESCRIPTION,
password_reset_form_desc: FORM_DESCRIPTION
},
$logistrationElement = $('#login-and-registration-container');
// Spy on AJAX requests
requests = AjaxHelpers.requests(that);
// Initialize the access view
view = new AccessView(_.extend(options, {el: $logistrationElement}));
// Mock the redirect call
spyOn( view, 'redirect' ).andCallFake( function() {} );
......@@ -92,7 +101,7 @@ define([
};
beforeEach(function() {
setFixtures('<div id="login-and-registration-container"></div>');
setFixtures('<div id="login-and-registration-container" class="login-register" />');
TemplateHelpers.installTemplate('templates/student_account/access');
TemplateHelpers.installTemplate('templates/student_account/login');
TemplateHelpers.installTemplate('templates/student_account/register');
......@@ -105,6 +114,10 @@ define([
window.analytics = jasmine.createSpyObj('analytics', ['track', 'page', 'pageview', 'trackLink']);
});
afterEach(function() {
Backbone.history.stop();
});
it('can initially display the login form', function() {
ajaxSpyAndInitialize(this, 'login');
......@@ -217,5 +230,5 @@ define([
});
});
}
);
});
}).call(this, define || RequireJS.define);
define([
;(function (define) {
'use strict';
define([
'jquery',
'jquery.url',
'utility',
'common/js/spec_helpers/ajax_helpers',
'js/student_account/views/FinishAuthView',
'js/student_account/enrollment',
'js/student_account/shoppingcart',
'js/student_account/emailoptin'
], function($, utility, AjaxHelpers, FinishAuthView, EnrollmentInterface, ShoppingCartInterface, EmailOptInInterface) {
'use strict';
],
function($, url, utility, AjaxHelpers, FinishAuthView, EnrollmentInterface, ShoppingCartInterface,
EmailOptInInterface) {
describe('FinishAuthView', function() {
var requests = null,
view = null,
......@@ -167,5 +172,5 @@ define([
expect( view.redirect ).toHaveBeenCalledWith( "/dashboard" );
});
});
}
);
});
}).call(this, define || RequireJS.define);
define([
;(function (define) {
'use strict';
define([
'jquery',
'underscore',
'common/js/spec_helpers/template_helpers',
'common/js/spec_helpers/ajax_helpers',
'js/student_account/views/HintedLoginView',
], function($, _, TemplateHelpers, AjaxHelpers, HintedLoginView) {
'use strict';
describe('edx.student.account.HintedLoginView', function() {
'js/student_account/views/HintedLoginView'
],
function($, _, TemplateHelpers, AjaxHelpers, HintedLoginView) {
describe('edx.student.account.HintedLoginView', function() {
var view = null,
requests = null,
PLATFORM_NAME = 'edX',
......@@ -76,7 +78,7 @@ define([
});
it('redirects the user to the hinted provider if the user clicks the proceed button', function() {
createHintedLoginView("oa2-google-oauth2");
createHintedLoginView('oa2-google-oauth2');
// Click the "Yes, proceed" button
$('.proceed-button').click();
......@@ -84,4 +86,5 @@ define([
expect(view.redirect).toHaveBeenCalledWith( '/auth/login/google-oauth2/?auth_entry=account_login' );
});
});
});
});
}).call(this, define || RequireJS.define);
define([
;(function (define) {
'use strict';
define([
'jquery',
'underscore',
'common/js/spec_helpers/template_helpers',
'js/student_account/views/InstitutionLoginView',
], function($, _, TemplateHelpers, InstitutionLoginView) {
'use strict';
describe('edx.student.account.InstitutionLoginView', function() {
'js/student_account/views/InstitutionLoginView'
],
function($, _, TemplateHelpers, InstitutionLoginView) {
describe('edx.student.account.InstitutionLoginView', function() {
var view = null,
PLATFORM_NAME = 'edX',
THIRD_PARTY_AUTH = {
......@@ -47,14 +49,16 @@ define([
});
it('displays a list of providers', function() {
var $google, $facebook;
createInstLoginView('login');
expect($('#institution_login-form').html()).not.toBe("");
var $google = $('li a:contains("Google")');
$google = $('li a:contains("Google")');
expect($google).toBeVisible();
expect($google).toHaveAttr(
'href', '/auth/login/google-oauth2/?auth_entry=account_login'
);
var $facebook = $('li a:contains("Facebook")');
$facebook = $('li a:contains("Facebook")');
expect($facebook).toBeVisible();
expect($facebook).toHaveAttr(
'href', '/auth/login/facebook/?auth_entry=account_login'
......@@ -62,14 +66,16 @@ define([
});
it('displays a list of providers', function() {
var $google, $facebook;
createInstLoginView('register');
expect($('#institution_login-form').html()).not.toBe("");
var $google = $('li a:contains("Google")');
$google = $('li a:contains("Google")');
expect($google).toBeVisible();
expect($google).toHaveAttr(
'href', '/auth/login/google-oauth2/?auth_entry=account_register'
);
var $facebook = $('li a:contains("Facebook")');
$facebook = $('li a:contains("Facebook")');
expect($facebook).toBeVisible();
expect($facebook).toHaveAttr(
'href', '/auth/login/facebook/?auth_entry=account_register'
......@@ -77,4 +83,5 @@ define([
});
});
});
});
}).call(this, define || RequireJS.define);
define([
;(function (define) {
'use strict';
define([
'jquery',
'underscore',
'common/js/spec_helpers/template_helpers',
......@@ -6,10 +8,10 @@ define([
'js/student_account/models/LoginModel',
'js/student_account/views/LoginView',
'js/student_account/models/PasswordResetModel'
], function($, _, TemplateHelpers, AjaxHelpers, LoginModel, LoginView, PasswordResetModel) {
'use strict';
describe('edx.student.account.LoginView', function() {
],
function($, _, TemplateHelpers, AjaxHelpers, LoginModel, LoginView, PasswordResetModel) {
describe('edx.student.account.LoginView', function() {
var model = null,
resetModel = null,
view = null,
......@@ -71,12 +73,12 @@ define([
defaultValue: '',
type: 'checkbox',
required: true,
instructions: "Agree to the terms of service.",
instructions: 'Agree to the terms of service.',
restrictions: {}
}
]
},
COURSE_ID = "edX/demoX/Fall";
COURSE_ID = 'edX/demoX/Fall';
var createLoginView = function(test) {
// Initialize the login model
......@@ -111,16 +113,16 @@ define([
};
var submitForm = function(validationSuccess) {
// Create a fake click event
var clickEvent = $.Event('click');
// Simulate manual entry of login form data
$('#login-email').val(USER_DATA.email);
$('#login-password').val(USER_DATA.password);
// Check the "Remember me" checkbox
// Check the 'Remember me' checkbox
$('#login-remember').prop('checked', USER_DATA.remember);
// Create a fake click event
var clickEvent = $.Event('click');
// If validationSuccess isn't passed, we avoid
// spying on `view.validate` twice
if ( !_.isUndefined(validationSuccess) ) {
......@@ -165,12 +167,14 @@ define([
});
it('sends analytics info containing the enrolled course ID', function() {
var expectedData;
createLoginView(this);
// Simulate that the user is attempting to enroll in a course
// by setting the course_id query string param.
spyOn($, 'url').andCallFake(function( param ) {
if (param === "?course_id") {
if (param === '?course_id') {
return encodeURIComponent( COURSE_ID );
}
});
......@@ -179,7 +183,7 @@ define([
submitForm( true );
// Verify that the client sent the course ID for analytics
var expectedData = {};
expectedData = {};
$.extend(expectedData, USER_DATA, {
analytics: JSON.stringify({
enroll_course_id: COURSE_ID
......@@ -262,4 +266,5 @@ define([
expect(authComplete).toBe(true);
});
});
});
});
}).call(this, define || RequireJS.define);
;(function (define) {
'use strict';
define([
'jquery',
'underscore',
'backbone',
'common/js/spec_helpers/template_helpers',
'common/js/spec_helpers/ajax_helpers',
'js/student_account/logistration_factory'
],
function($, _, Backbone, TemplateHelpers, AjaxHelpers, LogistrationFactory) {
describe('Logistration Factory', function() {
var FORM_DESCRIPTION = {
method: 'post',
submit_url: '/submit',
fields: [
{
name: 'email',
label: 'Email',
defaultValue: '',
type: 'text',
required: true,
placeholder: 'xsy@edx.org',
instructions: 'Enter your email here.',
restrictions: {}
},
{
name: 'username',
label: 'Username',
defaultValue: '',
type: 'text',
required: true,
placeholder: 'Xsy',
instructions: 'Enter your username here.',
restrictions: {
max_length: 200
}
}
]
};
var initializeLogistrationFactory = function(that, mode, nextUrl, finishAuthUrl) {
var options = {
initial_mode: mode,
third_party_auth: {
currentProvider: null,
providers: [],
secondaryProviders: [{name: 'provider'}],
finishAuthUrl: finishAuthUrl
},
login_redirect_url: nextUrl, // undefined for default
platform_name: 'edX',
login_form_desc: FORM_DESCRIPTION,
registration_form_desc: FORM_DESCRIPTION,
password_reset_form_desc: FORM_DESCRIPTION
};
// Initialize the logistration Factory
LogistrationFactory(options);
};
var assertForms = function(visibleForm, hiddenFormsList) {
expect($(visibleForm)).not.toHaveClass('hidden');
_.each(hiddenFormsList, function (hiddenForm) {
expect($(hiddenForm)).toHaveClass('hidden');
}, this);
};
beforeEach(function() {
setFixtures('<div id="login-and-registration-container" class="login-register" />');
TemplateHelpers.installTemplate('templates/student_account/access');
TemplateHelpers.installTemplate('templates/student_account/form_field');
TemplateHelpers.installTemplate('templates/student_account/login');
TemplateHelpers.installTemplate('templates/student_account/register');
TemplateHelpers.installTemplate('templates/student_account/password_reset')
});
afterEach(function() {
Backbone.history.stop();
});
it('can initially render the login form', function() {
var hiddenFormsList;
initializeLogistrationFactory(this, 'login');
/* Verify that only login form is expanded, and that the
/* all other logistration forms are collapsed.
*/
hiddenFormsList = [
'#register-form',
'#password-reset-form'
];
assertForms('#login-form', hiddenFormsList);
});
it('can initially render the registration form', function() {
var hiddenFormsList;
initializeLogistrationFactory(this, 'register');
/* Verify that only registration form is expanded, and that the
/* all other logistration forms are collapsed.
*/
hiddenFormsList = [
'#login-form',
'#password-reset-form'
];
assertForms('#register-form', hiddenFormsList);
});
it('can initially render the password reset form', function() {
var hiddenFormsList;
initializeLogistrationFactory(this, 'reset');
/* Verify that only password reset form is expanded, and that the
/* all other logistration forms are collapsed.
*/
hiddenFormsList = [
'#login-form',
'#register-form'
];
assertForms('#password-reset-form', hiddenFormsList);
});
});
});
}).call(this, define || RequireJS.define);
define([
;(function (define) {
'use strict';
define([
'jquery',
'underscore',
'common/js/spec_helpers/template_helpers',
'common/js/spec_helpers/ajax_helpers',
'js/student_account/models/PasswordResetModel',
'js/student_account/views/PasswordResetView',
], function($, _, TemplateHelpers, AjaxHelpers, PasswordResetModel, PasswordResetView) {
describe('edx.student.account.PasswordResetView', function() {
'use strict';
'js/student_account/views/PasswordResetView'
],
function($, _, TemplateHelpers, AjaxHelpers, PasswordResetModel, PasswordResetView) {
describe('edx.student.account.PasswordResetView', function() {
var model = null,
view = null,
requests = null,
......@@ -46,12 +48,12 @@ define([
};
var submitEmail = function(validationSuccess) {
// Simulate manual entry of an email address
$('#password-reset-email').val(EMAIL);
// Create a fake click event
var clickEvent = $.Event('click');
// Simulate manual entry of an email address
$('#password-reset-email').val(EMAIL);
// If validationSuccess isn't passed, we avoid
// spying on `view.validate` twice
if ( !_.isUndefined(validationSuccess) ) {
......@@ -141,5 +143,5 @@ define([
expect(view.$errors).toHaveClass('hidden');
});
});
}
);
});
}).call(this, define || RequireJS.define);
define([
;(function (define) {
'use strict';
define([
'jquery',
'underscore',
'common/js/spec_helpers/template_helpers',
'common/js/spec_helpers/ajax_helpers',
'js/student_account/models/RegisterModel',
'js/student_account/views/RegisterView'
], function($, _, TemplateHelpers, AjaxHelpers, RegisterModel, RegisterView) {
'use strict';
],
function($, _, TemplateHelpers, AjaxHelpers, RegisterModel, RegisterView) {
describe('edx.student.account.RegisterView', function() {
var model = null,
view = null,
requests = null,
authComplete = false,
PLATFORM_NAME = 'edX',
COURSE_ID = "edX/DemoX/Fall",
COURSE_ID = 'edX/DemoX/Fall',
USER_DATA = {
email: 'xsy@edx.org',
name: 'Xsy M. Education',
......@@ -198,6 +199,9 @@ define([
};
var submitForm = function(validationSuccess) {
// Create a fake click event
var clickEvent = $.Event('click');
// Simulate manual entry of registration form data
$('#register-email').val(USER_DATA.email);
$('#register-name').val(USER_DATA.name);
......@@ -212,9 +216,6 @@ define([
// Check the honor code checkbox
$('#register-honor_code').prop('checked', USER_DATA.honor_code);
// Create a fake click event
var clickEvent = $.Event('click');
// If validationSuccess isn't passed, we avoid
// spying on `view.validate` twice
if ( !_.isUndefined(validationSuccess) ) {
......@@ -258,12 +259,14 @@ define([
});
it('sends analytics info containing the enrolled course ID', function() {
var expectedData;
createRegisterView(this);
// Simulate that the user is attempting to enroll in a course
// by setting the course_id query string param.
spyOn($, 'url').andCallFake(function( param ) {
if (param === "?course_id") {
if (param === '?course_id') {
return encodeURIComponent( COURSE_ID );
}
});
......@@ -272,7 +275,7 @@ define([
submitForm( true );
// Verify that the client sent the course ID for analytics
var expectedData = {course_id: COURSE_ID};
expectedData = {course_id: COURSE_ID};
$.extend(expectedData, USER_DATA);
AjaxHelpers.expectRequest(
......@@ -349,4 +352,5 @@ define([
expect(view.$submitButton).toHaveAttr('disabled');
});
});
});
});
}).call(this, define || RequireJS.define);
var edx = edx || {};
(function($) {
'use strict';
edx.student = edx.student || {};
edx.student.account = edx.student.account || {};
var container = $('#login-and-registration-container');
return new edx.student.account.AccessView({
mode: container.data('initial-mode'),
thirdPartyAuth: container.data('third-party-auth'),
thirdPartyAuthHint: container.data('third-party-auth-hint'),
nextUrl: container.data('next-url'),
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);
;(function (define) {
'use strict';
define([
'jquery',
'js/student_account/views/AccessView'
],
function($, AccessView) {
return function(options) {
var $logistrationElement = $('#login-and-registration-container');
new AccessView(_.extend(options, {el: $logistrationElement}));
};
}
);
}).call(this, define || RequireJS.define);
var edx = edx || {};
(function($, Backbone) {
;(function (define) {
'use strict';
define([
'jquery',
'backbone',
'jquery.url'
], function ($, Backbone) {
edx.student = edx.student || {};
edx.student.account = edx.student.account || {};
edx.student.account.LoginModel = Backbone.Model.extend({
return Backbone.Model.extend({
defaults: {
email: '',
password: '',
......@@ -15,44 +14,44 @@ var edx = edx || {};
},
ajaxType: '',
urlRoot: '',
initialize: function( attributes, options ) {
initialize: function (attributes, options) {
this.ajaxType = options.method;
this.urlRoot = options.url;
},
sync: function(method, model) {
var headers = { 'X-CSRFToken': $.cookie('csrftoken') },
sync: function (method, model) {
var headers = {'X-CSRFToken': $.cookie('csrftoken')},
data = {},
analytics,
courseId = $.url( '?course_id' );
courseId = $.url('?course_id');
// If there is a course ID in the query string param,
// send that to the server as well so it can be included
// in analytics events.
if ( courseId ) {
if (courseId) {
analytics = JSON.stringify({
enroll_course_id: decodeURIComponent( courseId )
enroll_course_id: decodeURIComponent(courseId)
});
}
// Include all form fields and analytics info in the data sent to the server
$.extend( data, model.attributes, { analytics: analytics });
$.extend(data, model.attributes, {analytics: analytics});
$.ajax({
url: model.urlRoot,
type: model.ajaxType,
data: data,
headers: headers,
success: function() {
success: function () {
model.trigger('sync');
},
error: function( error ) {
error: function (error) {
model.trigger('error', error);
}
});
}
});
})(jQuery, Backbone);
});
}).call(this, define || RequireJS.define);
var edx = edx || {};
(function($, Backbone) {
;(function (define) {
'use strict';
define(['jquery', 'backbone'],
function($, Backbone) {
edx.student = edx.student || {};
edx.student.account = edx.student.account || {};
edx.student.account.PasswordResetModel = Backbone.Model.extend({
return Backbone.Model.extend({
defaults: {
email: ''
},
ajaxType: '',
urlRoot: '',
initialize: function( attributes, options ) {
......@@ -41,4 +35,5 @@ var edx = edx || {};
});
}
});
})(jQuery, Backbone);
});
}).call(this, define || RequireJS.define);
var edx = edx || {};
(function($, Backbone) {
;(function (define) {
'use strict';
define(['jquery', 'backbone', 'jquery.url'],
function($, Backbone) {
edx.student = edx.student || {};
edx.student.account = edx.student.account || {};
edx.student.account.RegisterModel = Backbone.Model.extend({
return Backbone.Model.extend({
defaults: {
email: '',
name: '',
......@@ -17,11 +13,9 @@ var edx = edx || {};
gender: '',
year_of_birth: '',
mailing_address: '',
goals: '',
goals: ''
},
ajaxType: '',
urlRoot: '',
initialize: function( attributes, options ) {
......@@ -58,4 +52,5 @@ var edx = edx || {};
});
}
});
})(jQuery, Backbone);
});
}).call(this, define || RequireJS.define);
var edx = edx || {};
(function($, _, _s, Backbone, History) {
;(function (define) {
'use strict';
define([
'jquery',
'utility',
'underscore',
'underscore.string',
'backbone',
'js/student_account/models/LoginModel',
'js/student_account/models/PasswordResetModel',
'js/student_account/models/RegisterModel',
'js/student_account/views/LoginView',
'js/student_account/views/PasswordResetView',
'js/student_account/views/RegisterView',
'js/student_account/views/InstitutionLoginView',
'js/student_account/views/HintedLoginView',
'js/vendor/history'
],
function($, utility, _, _s, Backbone, LoginModel, PasswordResetModel, RegisterModel, LoginView,
PasswordResetView, RegisterView, InstitutionLoginView, HintedLoginView) {
if (_.isUndefined(_s)) {
_s = _.str;
}
edx.student = edx.student || {};
edx.student.account = edx.student.account || {};
edx.student.account.AccessView = Backbone.View.extend({
el: '#login-and-registration-container',
return Backbone.View.extend({
tpl: '#access-tpl',
events: {
'click .form-toggle': 'toggleForm'
},
subview: {
login: {},
register: {},
......@@ -22,13 +35,12 @@ var edx = edx || {};
institutionLogin: {},
hintedLogin: {}
},
nextUrl: '/dashboard',
// The form currently loaded
activeForm: '',
initialize: function( obj ) {
initialize: function( options ) {
/* Mix non-conflicting functions from underscore.string
* (all but include, contains, and reverse) into the
* Underscore namespace
......@@ -37,34 +49,34 @@ var edx = edx || {};
this.tpl = $(this.tpl).html();
this.activeForm = obj.mode || 'login';
this.activeForm = options.initial_mode || 'login';
this.thirdPartyAuth = obj.thirdPartyAuth || {
this.thirdPartyAuth = options.third_party_auth || {
currentProvider: null,
providers: []
};
this.thirdPartyAuthHint = obj.thirdPartyAuthHint || null;
this.thirdPartyAuthHint = options.third_party_auth_hint || null;
if (obj.nextUrl) {
if (options.login_redirect_url) {
// Ensure that the next URL is internal for security reasons
if ( ! window.isExternal( obj.nextUrl ) ) {
this.nextUrl = obj.nextUrl;
if ( ! window.isExternal( options.login_redirect_url ) ) {
this.nextUrl = options.login_redirect_url;
}
}
this.formDescriptions = {
login: obj.loginFormDesc,
register: obj.registrationFormDesc,
reset: obj.passwordResetFormDesc,
login: options.login_form_desc,
register: options.registration_form_desc,
reset: options.password_reset_form_desc,
institution_login: null,
hinted_login: null
};
this.platformName = obj.platformName;
this.platformName = options.platform_name;
// The login view listens for 'sync' events from the reset model
this.resetModel = new edx.student.account.PasswordResetModel({}, {
this.resetModel = new PasswordResetModel({}, {
method: 'GET',
url: '#'
});
......@@ -88,7 +100,7 @@ var edx = edx || {};
postRender: function() {
//get & check current url hash part & load form accordingly
if (Backbone.history.getHash() === "forgot-password-modal") {
if (Backbone.history.getHash() === 'forgot-password-modal') {
this.resetPassword();
} else {
this.loadForm(this.activeForm);
......@@ -102,12 +114,12 @@ var edx = edx || {};
load: {
login: function( data ) {
var model = new edx.student.account.LoginModel({}, {
var model = new LoginModel({}, {
method: data.method,
url: data.submit_url
});
this.subview.login = new edx.student.account.LoginView({
this.subview.login = new LoginView({
fields: data.fields,
model: model,
resetModel: this.resetModel,
......@@ -127,7 +139,7 @@ var edx = edx || {};
this.resetModel.ajaxType = data.method;
this.resetModel.urlRoot = data.submit_url;
this.subview.passwordHelp = new edx.student.account.PasswordResetView({
this.subview.passwordHelp = new PasswordResetView({
fields: data.fields,
model: this.resetModel
});
......@@ -140,12 +152,12 @@ var edx = edx || {};
},
register: function( data ) {
var model = new edx.student.account.RegisterModel({}, {
var model = new RegisterModel({}, {
method: data.method,
url: data.submit_url
});
this.subview.register = new edx.student.account.RegisterView({
this.subview.register = new RegisterView({
fields: data.fields,
model: model,
thirdPartyAuth: this.thirdPartyAuth,
......@@ -157,7 +169,7 @@ var edx = edx || {};
},
institution_login: function ( unused ) {
this.subview.institutionLogin = new edx.student.account.InstitutionLoginView({
this.subview.institutionLogin = new InstitutionLoginView({
thirdPartyAuth: this.thirdPartyAuth,
platformName: this.platformName,
mode: this.activeForm
......@@ -167,7 +179,7 @@ var edx = edx || {};
},
hinted_login: function ( unused ) {
this.subview.hintedLogin = new edx.student.account.HintedLoginView({
this.subview.hintedLogin = new HintedLoginView({
thirdPartyAuth: this.thirdPartyAuth,
hintedProvider: this.thirdPartyAuthHint,
platformName: this.platformName
......@@ -178,9 +190,10 @@ var edx = edx || {};
},
passwordEmailSent: function() {
var $loginAnchorElement = $('#login-anchor');
this.element.hide( $(this.el).find('#password-reset-anchor') );
this.element.show( $('#login-anchor') );
this.element.scrollTop( $('#login-anchor') );
this.element.show( $loginAnchorElement );
this.element.scrollTop( $loginAnchorElement );
},
resetPassword: function() {
......@@ -207,7 +220,7 @@ var edx = edx || {};
});
// Load the form. Institution login is always refreshed since it changes based on the previous form.
if ( !this.form.isLoaded( $form ) || type == "institution_login") {
if ( !this.form.isLoaded( $form ) || type == 'institution_login') {
this.loadForm( type );
}
this.activeForm = type;
......@@ -218,13 +231,13 @@ var edx = edx || {};
this.element.scrollTop( $anchor );
// Update url without reloading page
if (type != "institution_login") {
if (type != 'institution_login') {
History.pushState( null, document.title, '/' + type + queryStr );
}
analytics.page( 'login_and_registration', type );
// Focus on the form
$("#" + type).focus();
$('#' + type).focus();
},
/**
......@@ -277,4 +290,5 @@ var edx = edx || {};
}
}
});
})(jQuery, _, _.str, Backbone, History);
});
}).call(this, define || RequireJS.define);
var edx = edx || {};
(function($, _, Backbone, gettext) {
;(function (define) {
'use strict';
edx.student = edx.student || {};
edx.student.account = edx.student.account || {};
edx.student.account.FormView = Backbone.View.extend({
define([
'jquery',
'underscore',
'backbone',
'js/utils/edx.utils.validate'
],
function($, _, Backbone, EdxUtilsValidate) {
return Backbone.View.extend({
tagName: 'form',
el: '',
......@@ -64,7 +66,6 @@ var edx = edx || {};
postRender: function() {
var $container = $(this.el);
this.$form = $container.find('form');
this.$errors = $container.find('.submission-error');
this.$submitButton = $container.find(this.submitButton);
......@@ -263,8 +264,8 @@ var edx = edx || {};
},
validate: function( $el ) {
return edx.utils.validate( $el );
return EdxUtilsValidate.validate( $el );
}
});
})(jQuery, _, Backbone, gettext);
});
}).call(this, define || RequireJS.define);
var edx = edx || {};
(function($, _, gettext) {
;(function (define) {
'use strict';
define(['jquery', 'underscore', 'backbone'],
function($, _, Backbone) {
edx.student = edx.student || {};
edx.student.account = edx.student.account || {};
edx.student.account.HintedLoginView = Backbone.View.extend({
return Backbone.View.extend({
el: '#hinted-login-form',
tpl: '#hinted_login-tpl',
......@@ -45,4 +42,5 @@ var edx = edx || {};
window.location.href = url;
}
});
})(jQuery, _, gettext);
});
}).call(this, define || RequireJS.define);
var edx = edx || {};
(function($, _, Backbone) {
;(function (define) {
'use strict';
define(['jquery', 'underscore', 'backbone'],
function($, _, Backbone) {
edx.student = edx.student || {};
edx.student.account = edx.student.account || {};
edx.student.account.InstitutionLoginView = Backbone.View.extend({
return Backbone.View.extend({
el: '#institution_login-form',
initialize: function( data ) {
......@@ -27,4 +24,5 @@ var edx = edx || {};
return this;
}
});
})(jQuery, _, Backbone);
});
}).call(this, define || RequireJS.define);
var edx = edx || {};
(function($, _, gettext) {
;(function (define) {
'use strict';
edx.student = edx.student || {};
edx.student.account = edx.student.account || {};
edx.student.account.LoginView = edx.student.account.FormView.extend({
define([
'jquery',
'underscore',
'js/student_account/views/FormView'
],
function($, _, FormView) {
return FormView.extend({
el: '#login-form',
tpl: '#login-tpl',
events: {
'click .js-login': 'submitForm',
'click .forgot-password': 'forgotPassword',
'click .login-provider': 'thirdPartyAuth'
},
formType: 'login',
requiredStr: '',
submitButton: '.js-login',
preRender: function( data ) {
......@@ -128,4 +124,6 @@ var edx = edx || {};
this.toggleDisableButton(false);
}
});
})(jQuery, _, gettext);
});
}).call(this, define || RequireJS.define);
var edx = edx || {};
(function($, gettext) {
;(function (define) {
'use strict';
define([
'jquery',
'js/student_account/views/FormView'
],
function($, FormView) {
edx.student = edx.student || {};
edx.student.account = edx.student.account || {};
edx.student.account.PasswordResetView = edx.student.account.FormView.extend({
return FormView.extend({
el: '#password-reset-form',
tpl: '#password_reset-tpl',
......@@ -44,5 +44,5 @@ var edx = edx || {};
this.stopListening();
}
});
})(jQuery, gettext);
});
}).call(this, define || RequireJS.define);
var edx = edx || {};
(function($, _, gettext) {
;(function (define) {
'use strict';
edx.student = edx.student || {};
edx.student.account = edx.student.account || {};
edx.student.account.RegisterView = edx.student.account.FormView.extend({
define([
'jquery',
'underscore',
'js/student_account/views/FormView'
],
function($, _, FormView) {
return FormView.extend({
el: '#register-form',
tpl: '#register-tpl',
......@@ -81,7 +82,7 @@ var edx = edx || {};
function(error_list) {
return _.map(
error_list,
function(error) { return "<li>" + error.user_message + "</li>"; }
function(error) { return '<li>' + error.user_message + '</li>'; }
);
}
)
......@@ -95,6 +96,7 @@ var edx = edx || {};
// The form did not get submitted due to validation errors.
$(this.el).show(); // Show in case the form was hidden for auto-submission
}
},
}
});
});
})(jQuery, _, gettext);
}).call(this, define || RequireJS.define);
......@@ -63,6 +63,7 @@ lib_paths:
- xmodule_js/common_static/js/test/i18n.js
- xmodule_js/common_static/js/vendor/date.js
- xmodule_js/common_static/js/vendor/moment.min.js
- xmodule_js/common_static/js/utils/edx.utils.validate.js
# Paths to source JavaScript files
src_paths:
......
......@@ -24,6 +24,7 @@
'js/groups/views/cohorts_dashboard_factory',
'js/search/course/course_search_factory',
'js/search/dashboard/dashboard_search_factory',
'js/student_account/logistration_factory',
'js/student_account/views/account_settings_factory',
'js/student_account/views/finish_auth_factory',
'js/student_profile/views/learner_profile_factory',
......
<%! from django.utils.translation import ugettext as _ %>
<%!
import json
from django.utils.translation import ugettext as _
from openedx.core.lib.json_utils import EscapedEdxJSONEncoder
%>
<%namespace name='static' file='/static_content.html'/>
<%inherit file="../main.html" />
......@@ -6,9 +10,10 @@
<%block name="pagetitle">${_("Sign in or Register")}</%block>
<%block name="js_extra">
<script src="${static.url('js/vendor/underscore.string.min.js')}"></script>
<script src="${static.url('js/vendor/history.js')}"></script>
<%static:js group='student_account'/>
<%static:require_module module_name="js/student_account/logistration_factory" class_name="LogistrationFactory">
var options = ${ json.dumps(data, cls=EscapedEdxJSONEncoder) };
LogistrationFactory(options);
</%static:require_module>
</%block>
<%block name="header_extras">
......@@ -20,17 +25,7 @@
</%block>
<div class="section-bkg-wrapper">
<div id="login-and-registration-container"
class="login-register"
data-initial-mode="${initial_mode}"
data-third-party-auth='${third_party_auth|h}'
data-third-party-auth-hint='${third_party_auth_hint}'
data-next-url='${login_redirect_url|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 id="login-and-registration-container" class="login-register" />
</div>
% if settings.FEATURES.get('ENABLE_COMBINED_LOGIN_REGISTRATION'):
......
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