remove studio signin and signup pages
This completes the work started in https://github.com/edx/edx-platform/pull/19453 to use the LMS login and registration for Studio, rather than Studio providing its own implementation. LMS login/registration are being used for the following reasons: 1. LMS logistration properly handles all SSO integrations. 2. A single logistration is simpler to maintain and understand. 3. Allows Studio to work more like all other IDAs that use LMS logistration. The original switch to use LMS logistration for Studio also added the toggle `DISABLE_STUDIO_SSO_OVER_LMS` to provide the community some additional time for switching. This commit removes this toggle, which at this point means all deployments will use the LMS logistration. This change requires sharing cookies across LMS and Studio. Should that prove to be a problem for certain Open edX instances, there are discussions of possible alternative solutions. See https://github.com/edx/edx-platform/pull/19845#issuecomment-559154256 Detailed changes: * Fix some Studio links that still went to old Studio signin and signup. * Remove DISABLE_STUDIO_SSO_OVER_LMS feature toggle. * Remove old studio signin and signup pages and templates. * Fix url name "login", which had different meanings for Studio and LMS. * Use the following settings: LOGIN_URL, FRONTEND_LOGIN_URL, FRONTEND_LOGOUT_URL, and FRONTEND_REGISTER_URL. * Redirect /signin and /signup to the LMS logistration. * Add custom metric `uses_pattern_library`. * Add custom metric `student_activate_account`. * Add Django Settings to allow /signin, /signup, and /login_post to be disabled once ready. This work also relates to ARCH-218 and DEPR-6. ARCH-1253
This commit is contained in:
@@ -2186,10 +2186,12 @@ class EntryPageTestCase(TestCase):
|
||||
self._test_page("/howitworks")
|
||||
|
||||
def test_signup(self):
|
||||
self._test_page("/signup")
|
||||
# deprecated signup url redirects to LMS register.
|
||||
self._test_page("/signup", 301)
|
||||
|
||||
def test_login(self):
|
||||
self._test_page("/signin")
|
||||
# deprecated signin url redirects to LMS login.
|
||||
self._test_page("/signin", 302)
|
||||
|
||||
def test_logout(self):
|
||||
# Logout redirects.
|
||||
@@ -2202,36 +2204,6 @@ class EntryPageTestCase(TestCase):
|
||||
self._test_page('/accessibility')
|
||||
|
||||
|
||||
class SigninPageTestCase(TestCase):
|
||||
"""
|
||||
Tests that the CSRF token is directly included in the signin form. This is
|
||||
important to make sure that the script is functional independently of any
|
||||
other script.
|
||||
"""
|
||||
|
||||
def test_csrf_token_is_present_in_form(self):
|
||||
# Expected html:
|
||||
# <form>
|
||||
# ...
|
||||
# <fieldset>
|
||||
# ...
|
||||
# <input name="csrfmiddlewaretoken" value="...">
|
||||
# ...
|
||||
# </fieldset>
|
||||
# ...
|
||||
# </form>
|
||||
response = self.client.get("/signin")
|
||||
csrf_token = response.cookies.get("csrftoken")
|
||||
form = lxml.html.fromstring(response.content).get_element_by_id("login_form")
|
||||
csrf_input_field = form.find(".//input[@name='csrfmiddlewaretoken']")
|
||||
|
||||
self.assertIsNotNone(csrf_token)
|
||||
self.assertIsNotNone(csrf_token.value)
|
||||
self.assertIsNotNone(csrf_input_field)
|
||||
|
||||
self.assertTrue(_compare_salted_tokens(csrf_token.value, csrf_input_field.attrib["value"]))
|
||||
|
||||
|
||||
def _create_course(test, course_key, course_data):
|
||||
"""
|
||||
Creates a course via an AJAX request and verifies the URL returned in the response.
|
||||
|
||||
@@ -7,19 +7,14 @@ import datetime
|
||||
import time
|
||||
|
||||
import mock
|
||||
import pytest
|
||||
from contentstore.tests.test_course_settings import CourseTestCase
|
||||
from contentstore.tests.utils import AjaxEnabledTestClient, parse_json, registration, user
|
||||
from ddt import data, ddt, unpack
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.cache import cache
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
from freezegun import freeze_time
|
||||
from pytz import UTC
|
||||
from six.moves import range
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
@@ -87,36 +82,7 @@ class ContentStoreTestCase(ModuleStoreTestCase):
|
||||
self.assertTrue(user(email).is_active)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_account_email_already_exists(django_db_use_migrations):
|
||||
"""
|
||||
This is tricky. Django's user model doesn't have a constraint on
|
||||
unique email addresses, but we *add* that constraint during the
|
||||
migration process:
|
||||
see common/djangoapps/student/migrations/0004_add_email_index.py
|
||||
|
||||
The behavior we *want* is for this account creation request
|
||||
to fail, due to this uniqueness constraint, but the request will
|
||||
succeed if the migrations have not run.
|
||||
|
||||
django_db_use_migration is a pytest fixture that tells us if
|
||||
migrations have been run. Since pytest fixtures don't play nice
|
||||
with TestCase objects this is a function and doesn't get to use
|
||||
assertRaises.
|
||||
"""
|
||||
if django_db_use_migrations:
|
||||
email = 'a@b.com'
|
||||
pw = 'xyz'
|
||||
username = 'testuser'
|
||||
User.objects.create_user(username, email, pw)
|
||||
|
||||
# Hack to use the _create_account shortcut
|
||||
case = ContentStoreTestCase()
|
||||
resp = case._create_account("abcdef", email, "password") # pylint: disable=protected-access
|
||||
|
||||
assert resp.status_code == 400, 'Migrations are run, but creating an account with duplicate email succeeded!'
|
||||
|
||||
|
||||
@ddt
|
||||
class AuthTestCase(ContentStoreTestCase):
|
||||
"""Check that various permissions-related things work"""
|
||||
|
||||
@@ -138,114 +104,6 @@ class AuthTestCase(ContentStoreTestCase):
|
||||
self.assertEqual(resp.status_code, expected)
|
||||
return resp
|
||||
|
||||
def test_public_pages_load(self):
|
||||
"""Make sure pages that don't require login load without error."""
|
||||
pages = (
|
||||
reverse('login'),
|
||||
reverse('signup'),
|
||||
)
|
||||
for page in pages:
|
||||
print(u"Checking '{0}'".format(page))
|
||||
self.check_page_get(page, 200)
|
||||
|
||||
def test_create_account_errors(self):
|
||||
# No post data -- should fail
|
||||
registration_url = reverse('user_api_registration')
|
||||
resp = self.client.post(registration_url, {})
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
|
||||
def test_create_account(self):
|
||||
self.create_account(self.username, self.email, self.pw)
|
||||
self.activate_user(self.email)
|
||||
|
||||
def test_create_account_username_already_exists(self):
|
||||
User.objects.create_user(self.username, self.email, self.pw)
|
||||
resp = self._create_account(self.username, "abc@def.com", "password")
|
||||
# we have a constraint on unique usernames, so this should fail
|
||||
self.assertEqual(resp.status_code, 409)
|
||||
|
||||
def test_create_account_pw_already_exists(self):
|
||||
User.objects.create_user(self.username, self.email, self.pw)
|
||||
resp = self._create_account("abcdef", "abc@def.com", self.pw)
|
||||
# we can have two users with the same password, so this should succeed
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
def test_login(self):
|
||||
self.create_account(self.username, self.email, self.pw)
|
||||
|
||||
# Not activated yet. Login should fail.
|
||||
self._login(self.email, self.pw)
|
||||
|
||||
self.activate_user(self.email)
|
||||
|
||||
# Now login should work
|
||||
self.login(self.email, self.pw)
|
||||
|
||||
def test_login_ratelimited(self):
|
||||
# try logging in 30 times, the default limit in the number of failed
|
||||
# login attempts in one 5 minute period before the rate gets limited
|
||||
for i in range(30):
|
||||
resp = self._login(self.email, 'wrong_password{0}'.format(i))
|
||||
self.assertEqual(resp.status_code, 403)
|
||||
resp = self._login(self.email, 'wrong_password')
|
||||
self.assertContains(resp, 'Too many failed login attempts.', status_code=403)
|
||||
|
||||
@override_settings(MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED=3)
|
||||
@override_settings(MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS=2)
|
||||
def test_excessive_login_failures(self):
|
||||
# try logging in 3 times, the account should get locked for 3 seconds
|
||||
# note we want to keep the lockout time short, so we don't slow down the tests
|
||||
|
||||
with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': True}):
|
||||
self.create_account(self.username, self.email, self.pw)
|
||||
self.activate_user(self.email)
|
||||
|
||||
for i in range(3):
|
||||
resp = self._login(self.email, 'wrong_password{0}'.format(i))
|
||||
self.assertContains(
|
||||
resp,
|
||||
'Email or password is incorrect.',
|
||||
status_code=403,
|
||||
)
|
||||
|
||||
# now the account should be locked
|
||||
|
||||
resp = self._login(self.email, 'wrong_password')
|
||||
self.assertContains(
|
||||
resp,
|
||||
'This account has been temporarily locked due to excessive login failures.',
|
||||
status_code=403,
|
||||
)
|
||||
|
||||
with freeze_time('2100-01-01'):
|
||||
self.login(self.email, self.pw)
|
||||
|
||||
# make sure the failed attempt counter gets reset on successful login
|
||||
resp = self._login(self.email, 'wrong_password')
|
||||
self.assertContains(
|
||||
resp,
|
||||
'Email or password is incorrect.',
|
||||
status_code=403,
|
||||
)
|
||||
|
||||
# account should not be locked out after just one attempt
|
||||
self.login(self.email, self.pw)
|
||||
|
||||
# do one more login when there is no bad login counter row at all in the database to
|
||||
# test the "ObjectNotFound" case
|
||||
self.login(self.email, self.pw)
|
||||
|
||||
def test_login_link_on_activation_age(self):
|
||||
self.create_account(self.username, self.email, self.pw)
|
||||
# we want to test the rendering of the activation page when the user isn't logged in
|
||||
self.client.logout()
|
||||
resp = self._activate_user(self.email)
|
||||
|
||||
# check the the HTML has links to the right login page. Note that this is merely a content
|
||||
# check and thus could be fragile should the wording change on this page
|
||||
expected = 'You can now <a href="' + reverse('login') + '">sign in</a>.'
|
||||
self.assertContains(resp, expected)
|
||||
|
||||
def test_private_pages_auth(self):
|
||||
"""Make sure pages that do require login work."""
|
||||
auth_pages = (
|
||||
@@ -259,7 +117,8 @@ class AuthTestCase(ContentStoreTestCase):
|
||||
)
|
||||
|
||||
# need an activated user
|
||||
self.test_create_account()
|
||||
self.create_account(self.username, self.email, self.pw)
|
||||
self.activate_user(self.email)
|
||||
|
||||
# Create a new session
|
||||
self.client = AjaxEnabledTestClient()
|
||||
@@ -278,14 +137,6 @@ class AuthTestCase(ContentStoreTestCase):
|
||||
print(u"Checking '{0}'".format(page))
|
||||
self.check_page_get(page, expected=200)
|
||||
|
||||
def test_index_auth(self):
|
||||
|
||||
# not logged in. Should return a redirect.
|
||||
resp = self.client.get_html('/home/')
|
||||
self.assertEqual(resp.status_code, 302)
|
||||
|
||||
# Logged in should work.
|
||||
|
||||
@override_settings(SESSION_INACTIVITY_TIMEOUT_IN_SECONDS=1)
|
||||
def test_inactive_session_timeout(self):
|
||||
"""
|
||||
@@ -308,37 +159,30 @@ class AuthTestCase(ContentStoreTestCase):
|
||||
resp = self.client.get_html(course_url)
|
||||
|
||||
# re-request, and we should get a redirect to login page
|
||||
self.assertRedirects(resp, settings.LOGIN_URL + '?next=/home/')
|
||||
self.assertRedirects(resp, settings.LOGIN_URL + '?next=/home/', target_status_code=302)
|
||||
|
||||
@mock.patch.dict(settings.FEATURES, {"ALLOW_PUBLIC_ACCOUNT_CREATION": False})
|
||||
def test_signup_button_index_page(self):
|
||||
@data(
|
||||
(True, 'assertContains'),
|
||||
(False, 'assertNotContains'))
|
||||
@unpack
|
||||
def test_signin_and_signup_buttons_index_page(self, allow_account_creation, assertion_method_name):
|
||||
"""
|
||||
Navigate to the home page and check the Sign Up button is hidden when ALLOW_PUBLIC_ACCOUNT_CREATION flag
|
||||
is turned off
|
||||
is turned off, and not when it is turned on. The Sign In button should always appear.
|
||||
"""
|
||||
response = self.client.get(reverse('homepage'))
|
||||
self.assertNotContains(response, '<a class="action action-signup" href="/signup">Sign Up</a>')
|
||||
|
||||
@mock.patch.dict(settings.FEATURES, {"ALLOW_PUBLIC_ACCOUNT_CREATION": False})
|
||||
def test_signup_button_login_page(self):
|
||||
"""
|
||||
Navigate to the login page and check the Sign Up button is hidden when ALLOW_PUBLIC_ACCOUNT_CREATION flag
|
||||
is turned off
|
||||
"""
|
||||
response = self.client.get(reverse('login'))
|
||||
self.assertNotContains(response, '<a class="action action-signup" href="/signup">Sign Up</a>')
|
||||
|
||||
@mock.patch.dict(settings.FEATURES, {"ALLOW_PUBLIC_ACCOUNT_CREATION": False})
|
||||
def test_signup_link_login_page(self):
|
||||
"""
|
||||
Navigate to the login page and check the Sign Up link is hidden when ALLOW_PUBLIC_ACCOUNT_CREATION flag
|
||||
is turned off
|
||||
"""
|
||||
response = self.client.get(reverse('login'))
|
||||
self.assertNotContains(
|
||||
response,
|
||||
'<a href="/signup" class="action action-signin">Don't have a Studio Account? Sign up!</a>'
|
||||
)
|
||||
with mock.patch.dict(settings.FEATURES, {"ALLOW_PUBLIC_ACCOUNT_CREATION": allow_account_creation}):
|
||||
response = self.client.get(reverse('homepage'))
|
||||
assertion_method = getattr(self, assertion_method_name)
|
||||
assertion_method(
|
||||
response,
|
||||
u'<a class="action action-signup" href="{}/register?next=http%3A%2F%2Ftestserver%2F">Sign Up</a>'.format( # pylint: disable=line-too-long
|
||||
settings.LMS_ROOT_URL
|
||||
)
|
||||
)
|
||||
self.assertContains(
|
||||
response,
|
||||
u'<a class="action action-signin" href="/signin_redirect_to_lms?next=http%3A%2F%2Ftestserver%2F">Sign In</a>' # pylint: disable=line-too-long
|
||||
)
|
||||
|
||||
|
||||
class ForumTestCase(CourseTestCase):
|
||||
|
||||
@@ -5,48 +5,25 @@ from __future__ import absolute_import
|
||||
|
||||
from django.conf import settings
|
||||
from django.shortcuts import redirect
|
||||
from django.template.context_processors import csrf
|
||||
from django.utils.http import urlquote_plus
|
||||
from django.views.decorators.clickjacking import xframe_options_deny
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from waffle.decorators import waffle_switch
|
||||
|
||||
from contentstore.config import waffle
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
|
||||
__all__ = ['signup', 'login_page', 'login_redirect_to_lms', 'howitworks', 'accessibility']
|
||||
__all__ = ['register_redirect_to_lms', 'login_redirect_to_lms', 'howitworks', 'accessibility']
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@xframe_options_deny
|
||||
def signup(request):
|
||||
def register_redirect_to_lms(request):
|
||||
"""
|
||||
Display the signup form.
|
||||
This view redirects to the LMS register view. It is used to temporarily keep the old
|
||||
Studio signup url alive.
|
||||
"""
|
||||
csrf_token = csrf(request)['csrf_token']
|
||||
if request.user.is_authenticated:
|
||||
return redirect('/course/')
|
||||
|
||||
return render_to_response('register.html', {'csrf': csrf_token})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@xframe_options_deny
|
||||
def login_page(request):
|
||||
"""
|
||||
Display the login form.
|
||||
"""
|
||||
csrf_token = csrf(request)['csrf_token']
|
||||
|
||||
return render_to_response(
|
||||
'login.html',
|
||||
{
|
||||
'csrf': csrf_token,
|
||||
'forgot_password_link': "//{base}/login#forgot-password-modal".format(base=settings.LMS_BASE),
|
||||
'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME),
|
||||
}
|
||||
register_url = '{register_url}{params}'.format(
|
||||
register_url=settings.FRONTEND_REGISTER_URL,
|
||||
params=_build_next_param(request),
|
||||
)
|
||||
return redirect(register_url, permanent=True)
|
||||
|
||||
|
||||
def login_redirect_to_lms(request):
|
||||
@@ -54,15 +31,25 @@ def login_redirect_to_lms(request):
|
||||
This view redirects to the LMS login view. It is used for Django's LOGIN_URL
|
||||
setting, which is where unauthenticated requests to protected endpoints are redirected.
|
||||
"""
|
||||
next_url = request.GET.get('next')
|
||||
absolute_next_url = request.build_absolute_uri(next_url)
|
||||
login_url = '{base_url}/login{params}'.format(
|
||||
base_url=settings.LMS_ROOT_URL,
|
||||
params='?next=' + urlquote_plus(absolute_next_url) if next_url else '',
|
||||
login_url = '{login_url}{params}'.format(
|
||||
login_url=settings.FRONTEND_LOGIN_URL,
|
||||
params=_build_next_param(request),
|
||||
)
|
||||
return redirect(login_url)
|
||||
|
||||
|
||||
def _build_next_param(request):
|
||||
""" Returns the next param to be used with login or register. """
|
||||
next_url = request.GET.get('next')
|
||||
next_url = next_url if next_url else settings.LOGIN_REDIRECT_URL
|
||||
if next_url:
|
||||
# Warning: do not use `build_absolute_uri` when `next_url` is empty because `build_absolute_uri` would
|
||||
# build use the login url for the next url, which would cause a login redirect loop.
|
||||
absolute_next_url = request.build_absolute_uri(next_url)
|
||||
return '?next=' + urlquote_plus(absolute_next_url)
|
||||
return ''
|
||||
|
||||
|
||||
def howitworks(request):
|
||||
"Proxy view"
|
||||
if request.user.is_authenticated:
|
||||
|
||||
@@ -92,11 +92,12 @@ class MaintenanceViewAccessTests(MaintenanceViewTestCase):
|
||||
|
||||
# Expect a redirect to the login page
|
||||
redirect_url = '{login_url}?next={original_url}'.format(
|
||||
login_url=reverse('login'),
|
||||
login_url=settings.LOGIN_URL,
|
||||
original_url=url,
|
||||
)
|
||||
|
||||
self.assertRedirects(response, redirect_url)
|
||||
# Studio login redirects to LMS login
|
||||
self.assertRedirects(response, redirect_url, target_status_code=302)
|
||||
|
||||
@ddt.data(*MAINTENANCE_URLS)
|
||||
def test_global_staff_access(self, url):
|
||||
|
||||
@@ -475,8 +475,6 @@ AWS_S3_CUSTOM_DOMAIN = 'SET-ME-PLEASE (ex. bucket-name.s3.amazonaws.com)'
|
||||
##############################################################################
|
||||
|
||||
EDX_ROOT_URL = ''
|
||||
LOGIN_REDIRECT_URL = EDX_ROOT_URL + '/home/'
|
||||
LOGIN_URL = reverse_lazy('login_redirect_to_lms')
|
||||
|
||||
# use the ratelimit backend to prevent brute force attacks
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
@@ -496,13 +494,21 @@ LOGGING_ENV = 'sandbox'
|
||||
LMS_BASE = 'localhost:18000'
|
||||
LMS_ROOT_URL = "https://localhost:18000"
|
||||
LMS_INTERNAL_ROOT_URL = LMS_ROOT_URL
|
||||
|
||||
LOGIN_REDIRECT_URL = EDX_ROOT_URL + '/home/'
|
||||
# TODO: Determine if LOGIN_URL could be set to the FRONTEND_LOGIN_URL value instead.
|
||||
LOGIN_URL = reverse_lazy('login_redirect_to_lms')
|
||||
FRONTEND_LOGIN_URL = lambda settings: settings.LMS_ROOT_URL + '/login'
|
||||
derived('FRONTEND_LOGIN_URL')
|
||||
FRONTEND_LOGOUT_URL = lambda settings: settings.LMS_ROOT_URL + '/logout'
|
||||
derived('FRONTEND_LOGOUT_URL')
|
||||
FRONTEND_REGISTER_URL = lambda settings: settings.LMS_ROOT_URL + '/register'
|
||||
derived('FRONTEND_REGISTER_URL')
|
||||
|
||||
LMS_ENROLLMENT_API_PATH = "/api/enrollment/v1/"
|
||||
ENTERPRISE_API_URL = LMS_INTERNAL_ROOT_URL + '/enterprise/api/v1/'
|
||||
ENTERPRISE_CONSENT_API_URL = LMS_INTERNAL_ROOT_URL + '/consent/api/v1/'
|
||||
ENTERPRISE_MARKETING_FOOTER_QUERY_PARAMS = {}
|
||||
FRONTEND_LOGIN_URL = LOGIN_URL
|
||||
FRONTEND_LOGOUT_URL = lambda settings: settings.LMS_ROOT_URL + '/logout'
|
||||
derived('FRONTEND_LOGOUT_URL')
|
||||
|
||||
# Public domain name of Studio (should be resolvable from the end-user's browser)
|
||||
CMS_BASE = 'localhost:18010'
|
||||
@@ -2122,3 +2128,44 @@ REGISTRATION_EXTRA_FIELDS = {
|
||||
'country': 'hidden',
|
||||
}
|
||||
EDXAPP_PARSE_KEYS = {}
|
||||
|
||||
###################### DEPRECATED URLS ##########################
|
||||
|
||||
# .. toggle_name: DISABLE_DEPRECATED_SIGNIN_URL
|
||||
# .. toggle_implementation: DjangoSetting
|
||||
# .. toggle_default: False
|
||||
# .. toggle_description: Toggle for removing the deprecated /signin url.
|
||||
# .. toggle_category: n/a
|
||||
# .. toggle_use_cases: incremental_release
|
||||
# .. toggle_creation_date: 2019-12-02
|
||||
# .. toggle_expiration_date: 2020-06-01
|
||||
# .. toggle_warnings: This url can be removed once it no longer has any real traffic.
|
||||
# .. toggle_tickets: ARCH-1253
|
||||
# .. toggle_status: supported
|
||||
DISABLE_DEPRECATED_SIGNIN_URL = False
|
||||
|
||||
# .. toggle_name: DISABLE_DEPRECATED_SIGNUP_URL
|
||||
# .. toggle_implementation: DjangoSetting
|
||||
# .. toggle_default: False
|
||||
# .. toggle_description: Toggle for removing the deprecated /signup url.
|
||||
# .. toggle_category: n/a
|
||||
# .. toggle_use_cases: incremental_release
|
||||
# .. toggle_creation_date: 2019-12-02
|
||||
# .. toggle_expiration_date: 2020-06-01
|
||||
# .. toggle_warnings: This url can be removed once it no longer has any real traffic.
|
||||
# .. toggle_tickets: ARCH-1253
|
||||
# .. toggle_status: supported
|
||||
DISABLE_DEPRECATED_SIGNUP_URL = False
|
||||
|
||||
# .. toggle_name: DISABLE_DEPRECATED_LOGIN_POST
|
||||
# .. toggle_implementation: DjangoSetting
|
||||
# .. toggle_default: False
|
||||
# .. toggle_description: Toggle for removing the deprecated /login_post url.
|
||||
# .. toggle_category: n/a
|
||||
# .. toggle_use_cases: incremental_release
|
||||
# .. toggle_creation_date: 2019-12-02
|
||||
# .. toggle_expiration_date: 2020-06-01
|
||||
# .. toggle_warnings: This url can be removed once it no longer has any real traffic. Note: We have permission to remove for traffic from user_agent including `mitx-quantum`.
|
||||
# .. toggle_tickets: ARCH-1253
|
||||
# .. toggle_status: supported
|
||||
DISABLE_DEPRECATED_LOGIN_POST = False
|
||||
|
||||
@@ -308,13 +308,6 @@ HEARTBEAT_CHECKS = ENV_TOKENS.get('HEARTBEAT_CHECKS', HEARTBEAT_CHECKS)
|
||||
HEARTBEAT_EXTENDED_CHECKS = ENV_TOKENS.get('HEARTBEAT_EXTENDED_CHECKS', HEARTBEAT_EXTENDED_CHECKS)
|
||||
HEARTBEAT_CELERY_TIMEOUT = ENV_TOKENS.get('HEARTBEAT_CELERY_TIMEOUT', HEARTBEAT_CELERY_TIMEOUT)
|
||||
|
||||
# Login using the LMS as the identity provider.
|
||||
# Turning the flag to True means that the LMS will NOT be used as the Identity Provider (idp)
|
||||
if FEATURES.get('DISABLE_STUDIO_SSO_OVER_LMS', False):
|
||||
LOGIN_URL = reverse_lazy('login')
|
||||
FRONTEND_LOGIN_URL = LOGIN_URL
|
||||
FRONTEND_LOGOUT_URL = reverse_lazy('logout')
|
||||
|
||||
LOGIN_REDIRECT_WHITELIST = [reverse_lazy('home')]
|
||||
|
||||
# Specific setting for the File Upload Service to store media in a bucket.
|
||||
|
||||
@@ -19,7 +19,6 @@ from .common import *
|
||||
import os
|
||||
from uuid import uuid4
|
||||
|
||||
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from path import Path as path
|
||||
|
||||
@@ -142,8 +141,6 @@ if os.environ.get('DISABLE_MIGRATIONS'):
|
||||
LMS_BASE = "localhost:8000"
|
||||
LMS_ROOT_URL = "http://{}".format(LMS_BASE)
|
||||
FEATURES['PREVIEW_LMS_BASE'] = "preview.localhost"
|
||||
LOGIN_URL = EDX_ROOT_URL + '/signin'
|
||||
|
||||
|
||||
CACHES = {
|
||||
# This is the cache used for most things. Askbot will not work without a
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
'js/factories/index',
|
||||
'js/factories/manage_users',
|
||||
'js/factories/outline',
|
||||
'js/factories/register',
|
||||
'js/factories/settings',
|
||||
'js/factories/settings_advanced',
|
||||
'js/factories/settings_graders',
|
||||
|
||||
@@ -22,7 +22,6 @@ window.edx.StringUtils = StringUtils;
|
||||
import './xblock/cms.runtime.v1_spec.js';
|
||||
import '../../../js/spec/factories/xblock_validation_spec.js';
|
||||
import '../../../js/spec/views/container_spec.js';
|
||||
import '../../../js/spec/views/login_studio_spec.js';
|
||||
import '../../../js/spec/views/modals/edit_xblock_spec.js';
|
||||
import '../../../js/spec/views/module_edit_spec.js';
|
||||
import '../../../js/spec/views/move_xblock_spec.js';
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import cookie from 'jquery.cookie';
|
||||
import utility from 'utility';
|
||||
import ViewUtils from 'common/js/components/utils/view_utils';
|
||||
|
||||
export default function LoginFactory(homepageURL) {
|
||||
function postJSON(url, data, callback) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
dataType: 'json',
|
||||
data: data,
|
||||
success: callback
|
||||
});
|
||||
}
|
||||
|
||||
// Clear the login error message when credentials are edited
|
||||
$('input#email').on('input', function () {
|
||||
$('#login_error').removeClass('is-shown');
|
||||
});
|
||||
|
||||
$('input#password').on('input', function () {
|
||||
$('#login_error').removeClass('is-shown');
|
||||
});
|
||||
|
||||
$('form#login_form').submit(function (event) {
|
||||
event.preventDefault();
|
||||
var $submitButton = $('#submit'),
|
||||
deferred = new $.Deferred(),
|
||||
promise = deferred.promise();
|
||||
ViewUtils.disableElementWhileRunning($submitButton, function () { return promise; });
|
||||
var submit_data = $('#login_form').serialize();
|
||||
|
||||
postJSON('/login_post', submit_data, function (json) {
|
||||
if (json.success) {
|
||||
var next = /next=([^&]*)/g.exec(decodeURIComponent(window.location.search));
|
||||
if (next && next.length > 1 && !isExternal(next[1])) {
|
||||
ViewUtils.redirect(next[1]);
|
||||
} else {
|
||||
ViewUtils.redirect(homepageURL);
|
||||
}
|
||||
} else if ($('#login_error').length === 0) {
|
||||
$('#login_form').prepend(
|
||||
'<div id="login_error" class="message message-status error">' +
|
||||
json.value +
|
||||
'</span></div>'
|
||||
);
|
||||
$('#login_error').addClass('is-shown');
|
||||
deferred.resolve();
|
||||
} else {
|
||||
$('#login_error')
|
||||
.stop()
|
||||
.addClass('is-shown')
|
||||
.html(json.value);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export { LoginFactory }
|
||||
@@ -1,59 +0,0 @@
|
||||
define(['jquery', 'jquery.cookie'], function($) {
|
||||
'use strict';
|
||||
return function() {
|
||||
$('form :input')
|
||||
.focus(function() {
|
||||
$('label[for="' + this.id + '"]').addClass('is-focused');
|
||||
})
|
||||
.blur(function() {
|
||||
$('label').removeClass('is-focused');
|
||||
});
|
||||
|
||||
$('form#register_form').submit(function(event) {
|
||||
event.preventDefault();
|
||||
var submit_data = $('#register_form').serialize();
|
||||
|
||||
$.ajax({
|
||||
url: '/create_account',
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: {'X-CSRFToken': $.cookie('csrftoken')},
|
||||
notifyOnError: false,
|
||||
data: submit_data,
|
||||
success: function(json) {
|
||||
location.href = '/course/';
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
var json = $.parseJSON(jqXHR.responseText);
|
||||
$('#register_error').html(json.value).stop().addClass('is-shown');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('input#password').blur(function() {
|
||||
var $formErrors = $('#password_error'),
|
||||
data = {
|
||||
password: $('#password').val()
|
||||
};
|
||||
|
||||
// Uninitialize the errors on blur
|
||||
$formErrors.empty();
|
||||
$formErrors.addClass('hidden');
|
||||
|
||||
$.ajax({
|
||||
url: '/api/user/v1/validation/registration',
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: data,
|
||||
success: function(json) {
|
||||
_.each(json.validation_decisions, function(value, key) {
|
||||
if (key === 'password' && value) {
|
||||
$formErrors.html(value);
|
||||
$formErrors.removeClass('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -1,35 +0,0 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import $ from 'jquery';
|
||||
import LoginFactory from 'js/factories/login';
|
||||
import AjaxHelpers from 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers';
|
||||
import ViewUtils from 'common/js/components/utils/view_utils';
|
||||
|
||||
describe('Studio Login Page', () => {
|
||||
var $submitButton;
|
||||
|
||||
beforeEach(function() {
|
||||
loadFixtures('mock/login.underscore');
|
||||
var login_factory = LoginFactory('/home/');
|
||||
$submitButton = $('#submit');
|
||||
});
|
||||
|
||||
it('disable the submit button once it is clicked', function() {
|
||||
spyOn(ViewUtils, 'redirect').and.callFake(function() {});
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
expect($submitButton).not.toHaveClass('is-disabled');
|
||||
$submitButton.click();
|
||||
AjaxHelpers.respondWithJson(requests, {success: true});
|
||||
expect($submitButton).toHaveClass('is-disabled');
|
||||
});
|
||||
|
||||
it('It will not disable the submit button if there are errors in ajax request', function() {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
expect($submitButton).not.toHaveClass('is-disabled');
|
||||
$submitButton.click();
|
||||
expect($submitButton).toHaveClass('is-disabled');
|
||||
AjaxHelpers.respondWithError(requests, {});
|
||||
expect($submitButton).not.toHaveClass('is-disabled');
|
||||
});
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
<%page expression_filter="h"/>
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
|
||||
<%block name="content">
|
||||
<div class="wrapper-mast wrapper sr">
|
||||
<header class="mast">
|
||||
<h1 class="page-header">
|
||||
${_("{studio_name} Account Activation").format(studio_name=settings.STUDIO_SHORT_NAME)}
|
||||
</h1>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content activation is-active">
|
||||
<article class="content-primary" role="main">
|
||||
</article>
|
||||
|
||||
<div class="notice notice-incontext notice-instruction has-actions">
|
||||
<div class="msg">
|
||||
<h2 class="title">${_("Your account is already active")}</h2>
|
||||
<div class="copy">
|
||||
<p>${_("This account, set up using {email}, has already been activated. Please sign in to start working within {studio_name}.".format(email=user.email, studio_name=settings.STUDIO_NAME))}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="list-actions">
|
||||
<li class="action-item">
|
||||
<a href="/signin" class="action-primary action-signin">
|
||||
${_("Sign into {studio_name}").format(studio_name=settings.STUDIO_SHORT_NAME)}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</%block>
|
||||
@@ -1,48 +0,0 @@
|
||||
<%!
|
||||
from openedx.core.djangolib.markup import Text
|
||||
from django.utils.translation import ugettext as _
|
||||
%>
|
||||
<%page expression_filter="h"/>
|
||||
<%inherit file="base.html" />
|
||||
|
||||
<%block name="content">
|
||||
<div class="wrapper-mast wrapper sr">
|
||||
<header class="mast">
|
||||
<h1 class="page-header">
|
||||
${Text(_("{studio_name} Account Activation")).format(
|
||||
studio_name=Text(settings.STUDIO_SHORT_NAME),
|
||||
)}
|
||||
</h1>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content activation is-complete">
|
||||
<article class="content-primary" role="main">
|
||||
</article>
|
||||
|
||||
<div class="notice notice-incontext notice-instruction has-actions">
|
||||
<div class="msg">
|
||||
<h1 class="title">${_("Your account activation is complete!")}</h1>
|
||||
<div class="copy">
|
||||
<p>
|
||||
${Text(_("Thank you for activating your account. You may now sign in and start using {studio_name} to author courses.")).format(
|
||||
studio_name=Text(settings.STUDIO_NAME)
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="list-actions">
|
||||
<li class="action-item">
|
||||
<a href="/signin" class="action-primary action-signin">
|
||||
${Text(_("Sign into {studio_name}")).format(
|
||||
studio_name=Text(settings.STUDIO_SHORT_NAME)
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</%block>
|
||||
@@ -1,42 +0,0 @@
|
||||
<%!
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
from django.utils.translation import ugettext as _
|
||||
%>
|
||||
<%page expression_filter="h"/>
|
||||
<%inherit file="base.html" />
|
||||
|
||||
<%block name="content">
|
||||
<div class="wrapper-mast wrapper sr">
|
||||
<header class="mast">
|
||||
<h1 class="page-header">
|
||||
${Text(_("{studio_name} Account Activation")).format(
|
||||
studio_name=Text(settings.STUDIO_SHORT_NAME)
|
||||
)}
|
||||
</h1>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content activation is-invalid">
|
||||
<article class="content-primary" role="main">
|
||||
</article>
|
||||
|
||||
<div class="notice notice-incontext notice-instruction has-actions">
|
||||
<div class="msg">
|
||||
<h1 class="title">${_("Your account activation is invalid")}</h1>
|
||||
<div class="copy">
|
||||
<p>${_("We're sorry. Something went wrong with your activation. Check to make sure the URL you went to was correct, as e-mail programs will sometimes split it into two lines.")}</p>
|
||||
<p>
|
||||
${Text(_("If you still have issues, contact {platform_name} Support. In the meantime, you can also return to {link_start}the {studio_name} homepage.{link_end}")).format(
|
||||
platform_name=Text(settings.PLATFORM_NAME),
|
||||
studio_name=Text(settings.STUDIO_NAME),
|
||||
link_start=HTML('<a href="/">'),
|
||||
link_end=HTML('</a>')
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</%block>
|
||||
@@ -3,7 +3,7 @@
|
||||
<%def name="online_help_token()"><% return "welcome" %></%def>
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
<%!
|
||||
from django.urls import reverse
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
%>
|
||||
@@ -161,10 +161,10 @@
|
||||
|
||||
<ul class="list-actions">
|
||||
<li class="action-item">
|
||||
<a href="${reverse('signup')}" class="action action-primary">${_("Sign Up & Start Making Your {platform_name} Course").format(platform_name=settings.PLATFORM_NAME)}</a>
|
||||
<a href="${settings.FRONTEND_REGISTER_URL}?next=${current_url}" class="action action-primary">${_("Sign Up & Start Making Your {platform_name} Course").format(platform_name=settings.PLATFORM_NAME)}</a>
|
||||
</li>
|
||||
<li class="action-item">
|
||||
<a href="${reverse('login')}" class="action action-secondary">${_("Already have a {studio_name} Account? Sign In").format(studio_name=settings.STUDIO_SHORT_NAME)}</a>
|
||||
<a href="${settings.LOGIN_URL}?next=${current_url}" class="action action-secondary">${_("Already have a {studio_name} Account? Sign In").format(studio_name=settings.STUDIO_SHORT_NAME)}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<div class="wrapper-content wrapper">
|
||||
<form id="login_form" method="post" action="login_post" onsubmit="return false;">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="csrf"/>
|
||||
<input id="email" type="email" name="email" placeholder="'example: username@domain.com'"/>
|
||||
<input id="password" type="password" name="password"/>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" id="submit" name="submit" class="action action-primary">Sign In</button>
|
||||
</div>
|
||||
<input name="honor_code" type="checkbox" value="true" checked="true" hidden="true">
|
||||
</form>
|
||||
</div>
|
||||
@@ -1,61 +0,0 @@
|
||||
<%namespace name='static' file='/static_content.html'/>
|
||||
<%page expression_filter="h"/>
|
||||
<%inherit file="base.html" />
|
||||
<%def name="online_help_token()"><% return "login" %></%def>
|
||||
<%!
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.djangolib.js_utils import js_escaped_string
|
||||
%>
|
||||
<%block name="title">${_("Sign In")}</%block>
|
||||
<%block name="bodyclass">not-signedin view-signin</%block>
|
||||
|
||||
<%block name="content">
|
||||
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<header>
|
||||
<h1 class="title title-1">${_("Sign In to {studio_name}").format(studio_name=settings.STUDIO_NAME)}</h1>
|
||||
% if static.get_value('ALLOW_PUBLIC_ACCOUNT_CREATION', settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION')):
|
||||
<a href="${reverse('signup')}" class="action action-signin">${_("Don't have a {studio_name} Account? Sign up!").format(studio_name=settings.STUDIO_SHORT_NAME)}</a>
|
||||
% endif
|
||||
</header>
|
||||
|
||||
<article class="content-primary" role="main">
|
||||
<form id="login_form" method="post" action="login_post" onsubmit="return false;">
|
||||
|
||||
<fieldset>
|
||||
<legend class="sr">${_("Required Information to Sign In to {studio_name}").format(studio_name=settings.STUDIO_NAME)}</legend>
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrf }" />
|
||||
|
||||
<ol class="list-input">
|
||||
<li class="field text required" id="field-email">
|
||||
<label for="email">${_("E-mail")}</label>
|
||||
<input id="email" type="email" name="email" placeholder="${_('example: username@domain.com')}"/>
|
||||
</li>
|
||||
|
||||
<li class="field text required" id="field-password">
|
||||
<label for="password">${_("Password")}</label>
|
||||
<input id="password" type="password" name="password" />
|
||||
<a href="${forgot_password_link}" class="action action-forgotpassword">${_("Forgot password?")}</a>
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" id="submit" name="submit" class="action action-primary">${_("Sign In to {studio_name}").format(studio_name=settings.STUDIO_NAME)}</button>
|
||||
</div>
|
||||
|
||||
<!-- no honor code for CMS, but need it because we're using the lms student object -->
|
||||
<input name="honor_code" type="checkbox" value="true" checked="true" hidden="true">
|
||||
</form>
|
||||
</article>
|
||||
</section>
|
||||
</div>
|
||||
</%block>
|
||||
|
||||
<%block name="page_bundle">
|
||||
<%static:webpack entry="js/factories/login">
|
||||
LoginFactory("${reverse('homepage') | n, js_escaped_string}");
|
||||
</%static:webpack>
|
||||
</%block>
|
||||
@@ -1,116 +0,0 @@
|
||||
<%inherit file="base.html" />
|
||||
<%def name="online_help_token()"><% return "register" %></%def>
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.urls import reverse
|
||||
%>
|
||||
|
||||
<%block name="title">${_("Sign Up")}</%block>
|
||||
<%block name="bodyclass">not-signedin view-signup</%block>
|
||||
|
||||
<%block name="content">
|
||||
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<header>
|
||||
<h1 class="title title-1">${_("Sign Up for {studio_name}").format(studio_name=settings.STUDIO_NAME)}</h1>
|
||||
<a href="${reverse('login')}" class="action action-signin">${_("Already have a {studio_name} Account? Sign in").format(studio_name=settings.STUDIO_SHORT_NAME)}</a>
|
||||
</header>
|
||||
|
||||
<p class="introduction">${_("Ready to start creating online courses? Sign up below and start creating your first {platform_name} course today.").format(platform_name=settings.PLATFORM_NAME)}</p>
|
||||
|
||||
<article class="content-primary" role="main">
|
||||
<form id="register_form" method="post">
|
||||
<div id="register_error" name="register_error" class="message message-status message-status error">
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend class="sr">${_("Required Information to Sign Up for {studio_name}").format(studio_name=settings.STUDIO_NAME)}</legend>
|
||||
|
||||
<ol class="list-input">
|
||||
<li class="field text required" id="field-email">
|
||||
<label for="email">${_("E-mail")}</label>
|
||||
## Translators: This is the placeholder text for a field that requests an email address.
|
||||
<input id="email" type="email" name="email" placeholder="${_("example: username@domain.com")}" />
|
||||
</li>
|
||||
|
||||
<li class="field text required" id="field-name">
|
||||
<label for="name">${_("Full Name")}</label>
|
||||
## Translators: This is the placeholder text for a field that requests the user's full name.
|
||||
<input id="name" type="text" name="name" placeholder="${_("example: Jane Doe")}" />
|
||||
</li>
|
||||
|
||||
<li class="field text required" id="field-username">
|
||||
<label for="username">${_("Public Username")}</label>
|
||||
## Translators: This is the placeholder text for a field that asks the user to pick a username
|
||||
<input id="username" type="text" name="username" placeholder="${_("example: JaneDoe")}" />
|
||||
<span class="tip tip-stacked">${_("This will be used in public discussions with your courses and in our edX101 support forums")}</span>
|
||||
</li>
|
||||
|
||||
<li class="field text required" id="field-password">
|
||||
<label for="password">${_("Password")}</label>
|
||||
<input id="password" type="password" name="password" />
|
||||
<span id="password_error" class="tip tip-error hidden" role="alert"></span>
|
||||
</li>
|
||||
|
||||
<li class="field-group">
|
||||
<div class="field text" id="field-location">
|
||||
<label for="location">${_("Your Location")}</label>
|
||||
<input class="short" id="location" type="text" name="location" />
|
||||
</div>
|
||||
|
||||
<div class="field text" id="field-language">
|
||||
<label for="language">${_("Preferred Language")}</label>
|
||||
<input class="short" id="language" type="text" name="language" />
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="field checkbox required" id="field-tos">
|
||||
<input id="tos" name="terms_of_service" type="checkbox" value="true" />
|
||||
<label for="tos">
|
||||
${_("I agree to the {a_start} Terms of Service {a_end}").format(a_start='<a data-rel="edx.org" href="{}">'.format(marketing_link('TOS')), a_end="</a>")}
|
||||
</label>
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" id="submit" name="submit" class="action action-primary">${_("Create My Account & Start Authoring Courses")}</button>
|
||||
</div>
|
||||
|
||||
<!-- no honor code for CMS, but need it because we're using the lms student object -->
|
||||
<input name="honor_code" type="checkbox" value="true" checked="true" hidden="true">
|
||||
</form>
|
||||
</article>
|
||||
|
||||
<aside class="content-supplementary" role="complementary">
|
||||
<h2 class="sr">${_("Common {studio_name} Questions").format(studio_name=settings.STUDIO_SHORT_NAME)}</h2>
|
||||
|
||||
<div class="bit">
|
||||
<h3 class="title-3">${_("Who is {studio_name} for?").format(studio_name=settings.STUDIO_SHORT_NAME)}</h3>
|
||||
<p>${_("{studio_name} is for anyone that wants to create online courses that leverage the global {platform_name} platform. Our users are often faculty members, teaching assistants and course staff, and members of instructional technology groups.").format(
|
||||
studio_name=settings.STUDIO_SHORT_NAME, platform_name=settings.PLATFORM_NAME,
|
||||
)}</p>
|
||||
</div>
|
||||
|
||||
<div class="bit">
|
||||
<h3 class="title-3">${_("How technically savvy do I need to be to create courses in {studio_name}?").format(studio_name=settings.STUDIO_SHORT_NAME)}</h3>
|
||||
<p>${_("{studio_name} is designed to be easy to use by almost anyone familiar with common web-based authoring environments (Wordpress, Moodle, etc.). No programming knowledge is required, but for some of the more advanced features, a technical background would be helpful. As always, we are here to help, so don't hesitate to dive right in.").format(
|
||||
studio_name=settings.STUDIO_SHORT_NAME,
|
||||
)}</p>
|
||||
</div>
|
||||
|
||||
<div class="bit">
|
||||
<h3 class="title-3">${_("I've never authored a course online before. Is there help?")}</h3>
|
||||
<p>${_("Absolutely. We have created an online course, edX101, that describes some best practices: from filming video, creating exercises, to the basics of running an online course. Additionally, we're always here to help, just drop us a note.")}</p>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
</div>
|
||||
</%block>
|
||||
|
||||
<%block name="requirejs">
|
||||
require(["js/factories/register"], function (RegisterFactory) {
|
||||
RegisterFactory();
|
||||
});
|
||||
</%block>
|
||||
@@ -1,7 +1,9 @@
|
||||
<%page expression_filter="h"/>
|
||||
<%inherit file="../base.html" />
|
||||
<%!
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.urls import reverse
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
%>
|
||||
|
||||
<%namespace name='static' file='../static_content.html'/>
|
||||
@@ -23,9 +25,14 @@ from django.urls import reverse
|
||||
%endif
|
||||
|
||||
%if user_logged_in:
|
||||
${_("Visit your {link_start}dashboard{link_end} to see your courses.").format(link_start='<a href="/">', link_end='</a>')}
|
||||
${Text(_("Visit your {link_start}dashboard{link_end} to see your courses.")).format(
|
||||
link_start=HTML('<a href="/">'),
|
||||
link_end=HTML('</a>')
|
||||
)}
|
||||
%else:
|
||||
${_("You can now {link_start}sign in{link_end}.").format(link_start='<a href="{url}">'.format(url=reverse('login')), link_end='</a>')}
|
||||
${Text(_("You can now {link_start}sign in{link_end}.")).format(
|
||||
link_start=HTML('<a href="{url}">').format(url=settings.LOGIN_URL, link_end=HTML('</a>'))
|
||||
)}
|
||||
%endif
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<h1>Check your email</h1>
|
||||
<p>${_("We've sent an email message to {email} with instructions for activating your account.").format(email=email)}</p>
|
||||
@@ -237,9 +237,6 @@
|
||||
</nav>
|
||||
|
||||
% else:
|
||||
<%
|
||||
register_url = settings.LMS_ROOT_URL + '/register'
|
||||
%>
|
||||
<nav class="nav-not-signedin nav-pitch" aria-label="${_('Account')}">
|
||||
<h2 class="sr-only">${_("Account Navigation")}</h2>
|
||||
<ol>
|
||||
@@ -248,11 +245,11 @@
|
||||
</li>
|
||||
% if static.get_value('ALLOW_PUBLIC_ACCOUNT_CREATION', settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION')):
|
||||
<li class="nav-item nav-not-signedin-signup">
|
||||
<a class="action action-signup" href="${register_url}?next=${current_url}">${_("Sign Up")}</a>
|
||||
<a class="action action-signup" href="${settings.FRONTEND_REGISTER_URL}?next=${current_url}">${_("Sign Up")}</a>
|
||||
</li>
|
||||
% endif
|
||||
<li class="nav-item nav-not-signedin-signin">
|
||||
<a class="action action-signin" href="${settings.FRONTEND_LOGIN_URL}?next=${current_url}">${_("Sign In")}</a>
|
||||
<a class="action action-signin" href="${settings.LOGIN_URL}?next=${current_url}">${_("Sign In")}</a>
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
@@ -4,10 +4,16 @@
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from edx_django_utils.monitoring import set_custom_metric
|
||||
from student.roles import GlobalStaff
|
||||
%>
|
||||
|
||||
% if uses_pattern_library:
|
||||
<%!
|
||||
## TODO: Use metric to see if CMS ever uses pattern library or if this case can be deleted.
|
||||
## NOTE: When removing, remove all references to `set_custom_metric`.
|
||||
set_custom_metric('uses_pattern_library', True)
|
||||
%>
|
||||
<div class="wrapper-user-menu dropdown-menu-container logged-in js-header-user-menu">
|
||||
<h3 class="title menu-title">
|
||||
<span class="sr-only">${_("Currently signed in as:")}</span>
|
||||
@@ -26,12 +32,15 @@
|
||||
</li>
|
||||
</%block>
|
||||
<li class="dropdown-item item has-block-link">
|
||||
<a class="action action-signout" href="${reverse('logout')}">${_("Sign Out")}</a>
|
||||
<a class="action action-signout" href="${settings.FRONTEND_LOGOUT_URL}">${_("Sign Out")}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
% else:
|
||||
<%!
|
||||
set_custom_metric('uses_pattern_library', False)
|
||||
%>
|
||||
<h3 class="title">
|
||||
<span class="label">
|
||||
<span class="label-prefix sr-only">${_("Currently signed in as:")}</span>
|
||||
|
||||
14
cms/urls.py
14
cms/urls.py
@@ -86,8 +86,6 @@ urlpatterns = [
|
||||
# restful api
|
||||
url(r'^$', contentstore.views.howitworks, name='homepage'),
|
||||
url(r'^howitworks$', contentstore.views.howitworks, name='howitworks'),
|
||||
url(r'^signup$', contentstore.views.signup, name='signup'),
|
||||
url(r'^signin$', contentstore.views.login_page, name='login'),
|
||||
url(r'^signin_redirect_to_lms$', contentstore.views.login_redirect_to_lms, name='login_redirect_to_lms'),
|
||||
url(r'^request_course_creator$', contentstore.views.request_course_creator, name='request_course_creator'),
|
||||
url(r'^course_team/{}(?:/(?P<email>.+))?$'.format(COURSELIKE_KEY_PATTERN),
|
||||
@@ -180,6 +178,18 @@ urlpatterns = [
|
||||
url(r'^accessibility$', contentstore.views.accessibility, name='accessibility'),
|
||||
]
|
||||
|
||||
if not settings.DISABLE_DEPRECATED_SIGNIN_URL:
|
||||
# TODO: Remove deprecated signin url when traffic proves it is no longer in use
|
||||
urlpatterns += [
|
||||
url(r'^signin$', contentstore.views.login_redirect_to_lms),
|
||||
]
|
||||
|
||||
if not settings.DISABLE_DEPRECATED_SIGNUP_URL:
|
||||
# TODO: Remove deprecated signup url when traffic proves it is no longer in use
|
||||
urlpatterns += [
|
||||
url(r'^signup$', contentstore.views.register_redirect_to_lms, name='register_redirect_to_lms'),
|
||||
]
|
||||
|
||||
JS_INFO_DICT = {
|
||||
'domain': 'djangojs',
|
||||
# We need to explicitly include external Django apps that are not in LOCALE_PATHS.
|
||||
|
||||
Reference in New Issue
Block a user