diff --git a/cms/envs/test.py b/cms/envs/test.py
index 6a7c17b001fe..500c8d538d39 100644
--- a/cms/envs/test.py
+++ b/cms/envs/test.py
@@ -26,7 +26,8 @@
from .common import *
# import settings from LMS for consistent behavior with CMS
-from lms.envs.test import ( # pylint: disable=wrong-import-order
+from lms.envs.test import ( # pylint: disable=wrong-import-order, disable=unused-import
+ ACCOUNT_MICROFRONTEND_URL,
COMPREHENSIVE_THEME_DIRS, # unimport:skip
DEFAULT_FILE_STORAGE,
ECOMMERCE_API_URL,
@@ -35,8 +36,10 @@
LOGIN_ISSUE_SUPPORT_LINK,
MEDIA_ROOT,
MEDIA_URL,
+ ORDER_HISTORY_MICROFRONTEND_URL,
PLATFORM_DESCRIPTION,
PLATFORM_NAME,
+ PROFILE_MICROFRONTEND_URL,
REGISTRATION_EXTRA_FIELDS,
GRADES_DOWNLOAD,
SITE_NAME,
@@ -51,28 +54,26 @@
STUDIO_SHORT_NAME = gettext_lazy("𝓢𝓽𝓾𝓭𝓲𝓸")
# Allow all hosts during tests, we use a lot of different ones all over the codebase.
-ALLOWED_HOSTS = [
- '*'
-]
+ALLOWED_HOSTS = ["*"]
# mongo connection settings
-MONGO_PORT_NUM = int(os.environ.get('EDXAPP_TEST_MONGO_PORT', '27017'))
-MONGO_HOST = os.environ.get('EDXAPP_TEST_MONGO_HOST', 'localhost')
+MONGO_PORT_NUM = int(os.environ.get("EDXAPP_TEST_MONGO_PORT", "27017"))
+MONGO_HOST = os.environ.get("EDXAPP_TEST_MONGO_HOST", "localhost")
THIS_UUID = uuid4().hex[:5]
-TEST_ROOT = path('test_root')
+TEST_ROOT = path("test_root")
# Want static files in the same dir for running on jenkins.
STATIC_ROOT = TEST_ROOT / "staticfiles"
-WEBPACK_LOADER['DEFAULT']['STATS_FILE'] = STATIC_ROOT / "webpack-stats.json"
+WEBPACK_LOADER["DEFAULT"]["STATS_FILE"] = STATIC_ROOT / "webpack-stats.json"
GITHUB_REPO_ROOT = TEST_ROOT / "data"
DATA_DIR = TEST_ROOT / "data"
COMMON_TEST_DATA_ROOT = COMMON_ROOT / "test" / "data"
# For testing "push to lms"
-FEATURES['ENABLE_EXPORT_GIT'] = True
+FEATURES["ENABLE_EXPORT_GIT"] = True
GIT_REPO_EXPORT_DIR = TEST_ROOT / "export_course_repos"
# TODO (cpennington): We need to figure out how envs/test.py can inject things into common.py so that we don't have to repeat this sort of thing # lint-amnesty, pylint: disable=line-too-long
@@ -90,51 +91,47 @@
# If we don't add these settings, then Django templates that can't
# find pipelined assets will raise a ValueError.
# http://stackoverflow.com/questions/12816941/unit-testing-with-django-pipeline
-STATICFILES_STORAGE = 'pipeline.storage.NonPackagingPipelineStorage'
+STATICFILES_STORAGE = "pipeline.storage.NonPackagingPipelineStorage"
STATIC_URL = "/static/"
# Update module store settings per defaults for tests
update_module_store_settings(
MODULESTORE,
module_store_options={
- 'default_class': 'xmodule.hidden_block.HiddenBlock',
- 'fs_root': TEST_ROOT / "data",
+ "default_class": "xmodule.hidden_block.HiddenBlock",
+ "fs_root": TEST_ROOT / "data",
},
doc_store_settings={
- 'db': f'test_xmodule_{THIS_UUID}',
- 'host': MONGO_HOST,
- 'port': MONGO_PORT_NUM,
- 'collection': 'test_modulestore',
+ "db": f"test_xmodule_{THIS_UUID}",
+ "host": MONGO_HOST,
+ "port": MONGO_PORT_NUM,
+ "collection": "test_modulestore",
},
)
CONTENTSTORE = {
- 'ENGINE': 'xmodule.contentstore.mongo.MongoContentStore',
- 'DOC_STORE_CONFIG': {
- 'host': MONGO_HOST,
- 'db': f'test_xcontent_{THIS_UUID}',
- 'port': MONGO_PORT_NUM,
- 'collection': 'dont_trip',
+ "ENGINE": "xmodule.contentstore.mongo.MongoContentStore",
+ "DOC_STORE_CONFIG": {
+ "host": MONGO_HOST,
+ "db": f"test_xcontent_{THIS_UUID}",
+ "port": MONGO_PORT_NUM,
+ "collection": "dont_trip",
},
# allow for additional options that can be keyed on a name, e.g. 'trashcan'
- 'ADDITIONAL_OPTIONS': {
- 'trashcan': {
- 'bucket': 'trash_fs'
- }
- }
+ "ADDITIONAL_OPTIONS": {"trashcan": {"bucket": "trash_fs"}},
}
DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': TEST_ROOT / "db" / "cms.db",
- 'ATOMIC_REQUESTS': True,
+ "default": {
+ "ENGINE": "django.db.backends.sqlite3",
+ "NAME": TEST_ROOT / "db" / "cms.db",
+ "ATOMIC_REQUESTS": True,
},
}
LMS_BASE = "localhost:8000"
LMS_ROOT_URL = f"http://{LMS_BASE}"
-FEATURES['PREVIEW_LMS_BASE'] = "preview.localhost"
+FEATURES["PREVIEW_LMS_BASE"] = "preview.localhost"
CMS_BASE = "localhost:8001"
CMS_ROOT_URL = f"http://{CMS_BASE}"
@@ -145,49 +142,47 @@
CACHES = {
# This is the cache used for most things.
# In staging/prod envs, the sessions also live here.
- 'default': {
- 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
- 'LOCATION': 'edx_loc_mem_cache',
- 'KEY_FUNCTION': 'common.djangoapps.util.memcache.safe_key',
+ "default": {
+ "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
+ "LOCATION": "edx_loc_mem_cache",
+ "KEY_FUNCTION": "common.djangoapps.util.memcache.safe_key",
},
-
# The general cache is what you get if you use our util.cache. It's used for
# things like caching the course.xml file for different A/B test groups.
# We set it to be a DummyCache to force reloading of course.xml in dev.
# In staging environments, we would grab VERSION from data uploaded by the
# push process.
- 'general': {
- 'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
- 'KEY_PREFIX': 'general',
- 'VERSION': 4,
- 'KEY_FUNCTION': 'common.djangoapps.util.memcache.safe_key',
+ "general": {
+ "BACKEND": "django.core.cache.backends.dummy.DummyCache",
+ "KEY_PREFIX": "general",
+ "VERSION": 4,
+ "KEY_FUNCTION": "common.djangoapps.util.memcache.safe_key",
},
-
- 'mongo_metadata_inheritance': {
- 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
- 'LOCATION': os.path.join(tempfile.gettempdir(), 'mongo_metadata_inheritance'),
- 'TIMEOUT': 300,
- 'KEY_FUNCTION': 'common.djangoapps.util.memcache.safe_key',
+ "mongo_metadata_inheritance": {
+ "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
+ "LOCATION": os.path.join(tempfile.gettempdir(), "mongo_metadata_inheritance"),
+ "TIMEOUT": 300,
+ "KEY_FUNCTION": "common.djangoapps.util.memcache.safe_key",
},
- 'loc_cache': {
- 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
- 'LOCATION': 'edx_location_mem_cache',
+ "loc_cache": {
+ "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
+ "LOCATION": "edx_location_mem_cache",
},
- 'course_structure_cache': {
- 'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
+ "course_structure_cache": {
+ "BACKEND": "django.core.cache.backends.dummy.DummyCache",
},
}
################################# CELERY ######################################
CELERY_ALWAYS_EAGER = True
-CELERY_RESULT_BACKEND = 'django-cache'
+CELERY_RESULT_BACKEND = "django-cache"
CLEAR_REQUEST_CACHE_ON_TASK_COMPLETION = False
# test_status_cancel in cms/cms_user_tasks/test.py is failing without this
# @override_setting for BROKER_URL is not working in testcase, so updating here
-BROKER_URL = 'memory://localhost/'
+BROKER_URL = "memory://localhost/"
########################### Server Ports ###################################
@@ -202,99 +197,99 @@
################### Make tests faster
# http://slacy.com/blog/2012/04/make-your-tests-faster-in-django-1-4/
PASSWORD_HASHERS = [
- 'django.contrib.auth.hashers.SHA1PasswordHasher',
- 'django.contrib.auth.hashers.MD5PasswordHasher',
+ "django.contrib.auth.hashers.SHA1PasswordHasher",
+ "django.contrib.auth.hashers.MD5PasswordHasher",
]
# No segment key
CMS_SEGMENT_KEY = None
-FEATURES['DISABLE_SET_JWT_COOKIES_FOR_TESTS'] = True
+FEATURES["DISABLE_SET_JWT_COOKIES_FOR_TESTS"] = True
-FEATURES['ENABLE_SERVICE_STATUS'] = True
+FEATURES["ENABLE_SERVICE_STATUS"] = True
# Toggles embargo on for testing
-FEATURES['EMBARGO'] = True
+FEATURES["EMBARGO"] = True
TEST_THEME = COMMON_ROOT / "test" / "test-theme"
# For consistency in user-experience, keep the value of this setting in sync with
# the one in lms/envs/test.py
-FEATURES['ENABLE_DISCUSSION_SERVICE'] = False
+FEATURES["ENABLE_DISCUSSION_SERVICE"] = False
# Enable a parental consent age limit for testing
PARENTAL_CONSENT_AGE_LIMIT = 13
# Enable certificates for the tests
-FEATURES['CERTIFICATES_HTML_VIEW'] = True
+FEATURES["CERTIFICATES_HTML_VIEW"] = True
# Enable content libraries code for the tests
-FEATURES['ENABLE_CONTENT_LIBRARIES'] = True
+FEATURES["ENABLE_CONTENT_LIBRARIES"] = True
-FEATURES['ENABLE_EDXNOTES'] = True
+FEATURES["ENABLE_EDXNOTES"] = True
# MILESTONES
-FEATURES['MILESTONES_APP'] = True
+FEATURES["MILESTONES_APP"] = True
# ENTRANCE EXAMS
-FEATURES['ENTRANCE_EXAMS'] = True
+FEATURES["ENTRANCE_EXAMS"] = True
ENTRANCE_EXAM_MIN_SCORE_PCT = 50
-VIDEO_CDN_URL = {
- 'CN': 'http://api.xuetangx.com/edx/video?s3_url='
-}
+VIDEO_CDN_URL = {"CN": "http://api.xuetangx.com/edx/video?s3_url="}
# Courseware Search Index
-FEATURES['ENABLE_COURSEWARE_INDEX'] = True
-FEATURES['ENABLE_LIBRARY_INDEX'] = True
+FEATURES["ENABLE_COURSEWARE_INDEX"] = True
+FEATURES["ENABLE_LIBRARY_INDEX"] = True
SEARCH_ENGINE = "search.tests.mock_search_engine.MockSearchEngine"
-FEATURES['ENABLE_ENROLLMENT_TRACK_USER_PARTITION'] = True
+FEATURES["ENABLE_ENROLLMENT_TRACK_USER_PARTITION"] = True
########################## AUTHOR PERMISSION #######################
-FEATURES['ENABLE_CREATOR_GROUP'] = False
+FEATURES["ENABLE_CREATOR_GROUP"] = False
# teams feature
-FEATURES['ENABLE_TEAMS'] = True
+FEATURES["ENABLE_TEAMS"] = True
# Dummy secret key for dev/test
-SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
+SECRET_KEY = "85920908f28904ed733fe576320db18cabd7b6cd"
######### custom courses #########
INSTALLED_APPS += [
- 'openedx.core.djangoapps.ccxcon.apps.CCXConnectorConfig',
- 'common.djangoapps.third_party_auth.apps.ThirdPartyAuthConfig',
+ "openedx.core.djangoapps.ccxcon.apps.CCXConnectorConfig",
+ "common.djangoapps.third_party_auth.apps.ThirdPartyAuthConfig",
]
-FEATURES['CUSTOM_COURSES_EDX'] = True
+FEATURES["CUSTOM_COURSES_EDX"] = True
########################## VIDEO IMAGE STORAGE ############################
VIDEO_IMAGE_SETTINGS = dict(
- VIDEO_IMAGE_MAX_BYTES=2 * 1024 * 1024, # 2 MB
- VIDEO_IMAGE_MIN_BYTES=2 * 1024, # 2 KB
+ VIDEO_IMAGE_MAX_BYTES=2 * 1024 * 1024, # 2 MB
+ VIDEO_IMAGE_MIN_BYTES=2 * 1024, # 2 KB
STORAGE_KWARGS=dict(
location=MEDIA_ROOT,
),
- DIRECTORY_PREFIX='video-images/',
+ DIRECTORY_PREFIX="video-images/",
BASE_URL=MEDIA_URL,
)
-VIDEO_IMAGE_DEFAULT_FILENAME = 'default_video_image.png'
+VIDEO_IMAGE_DEFAULT_FILENAME = "default_video_image.png"
########################## VIDEO TRANSCRIPTS STORAGE ############################
VIDEO_TRANSCRIPTS_SETTINGS = dict(
- VIDEO_TRANSCRIPTS_MAX_BYTES=3 * 1024 * 1024, # 3 MB
+ VIDEO_TRANSCRIPTS_MAX_BYTES=3 * 1024 * 1024, # 3 MB
STORAGE_KWARGS=dict(
location=MEDIA_ROOT,
base_url=MEDIA_URL,
),
- DIRECTORY_PREFIX='video-transcripts/',
+ DIRECTORY_PREFIX="video-transcripts/",
)
####################### Plugin Settings ##########################
# pylint: disable=wrong-import-position, wrong-import-order
from edx_django_utils.plugins import add_plugins
+
# pylint: disable=wrong-import-position, wrong-import-order
from openedx.core.djangoapps.plugins.constants import ProjectType, SettingsType
+
add_plugins(__name__, ProjectType.CMS, SettingsType.TEST)
########################## Derive Any Derived Settings #######################
@@ -310,22 +305,22 @@
# Used in edx-proctoring for ID generation in lieu of SECRET_KEY - dummy value
# (ref MST-637)
-PROCTORING_USER_OBFUSCATION_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
+PROCTORING_USER_OBFUSCATION_KEY = "85920908f28904ed733fe576320db18cabd7b6cd"
##### LOGISTRATION RATE LIMIT SETTINGS #####
-LOGISTRATION_RATELIMIT_RATE = '5/5m'
-LOGISTRATION_PER_EMAIL_RATELIMIT_RATE = '6/5m'
-LOGISTRATION_API_RATELIMIT = '5/m'
+LOGISTRATION_RATELIMIT_RATE = "5/5m"
+LOGISTRATION_PER_EMAIL_RATELIMIT_RATE = "6/5m"
+LOGISTRATION_API_RATELIMIT = "5/m"
-REGISTRATION_VALIDATION_RATELIMIT = '5/minute'
-REGISTRATION_RATELIMIT = '5/minute'
-OPTIONAL_FIELD_API_RATELIMIT = '5/m'
+REGISTRATION_VALIDATION_RATELIMIT = "5/minute"
+REGISTRATION_RATELIMIT = "5/minute"
+OPTIONAL_FIELD_API_RATELIMIT = "5/m"
-RESET_PASSWORD_TOKEN_VALIDATE_API_RATELIMIT = '2/m'
-RESET_PASSWORD_API_RATELIMIT = '2/m'
+RESET_PASSWORD_TOKEN_VALIDATE_API_RATELIMIT = "2/m"
+RESET_PASSWORD_API_RATELIMIT = "2/m"
############### Settings for proctoring ###############
-PROCTORING_USER_OBFUSCATION_KEY = 'test_key'
+PROCTORING_USER_OBFUSCATION_KEY = "test_key"
#################### Network configuration ####################
# Tests are not behind any proxies
@@ -339,10 +334,5 @@
############## openedx-learning (Learning Core) config ##############
OPENEDX_LEARNING = {
- 'MEDIA': {
- 'BACKEND': 'django.core.files.storage.InMemoryStorage',
- 'OPTIONS': {
- 'location': MEDIA_ROOT + "_private"
- }
- }
+ "MEDIA": {"BACKEND": "django.core.files.storage.InMemoryStorage", "OPTIONS": {"location": MEDIA_ROOT + "_private"}}
}
diff --git a/common/djangoapps/student/tests/test_filters.py b/common/djangoapps/student/tests/test_filters.py
index 376595a8507b..bf79ed7ae402 100644
--- a/common/djangoapps/student/tests/test_filters.py
+++ b/common/djangoapps/student/tests/test_filters.py
@@ -1,6 +1,7 @@
"""
Test that various filters are fired for models/views in the student app.
"""
+from django.conf import settings
from django.http import HttpResponse
from django.test import override_settings
from django.urls import reverse
@@ -421,7 +422,7 @@ def test_dashboard_redirect_account_settings(self):
response = self.client.get(self.dashboard_url)
self.assertEqual(status.HTTP_302_FOUND, response.status_code)
- self.assertEqual(reverse("account_settings"), response.url)
+ self.assertEqual(settings.ACCOUNT_MICROFRONTEND_URL, response.url)
@override_settings(
OPEN_EDX_FILTERS_CONFIG={
diff --git a/common/djangoapps/student/tests/test_views.py b/common/djangoapps/student/tests/test_views.py
index 15ecdab23642..b63c522bbd0f 100644
--- a/common/djangoapps/student/tests/test_views.py
+++ b/common/djangoapps/student/tests/test_views.py
@@ -233,7 +233,7 @@ def test_redirect_account_settings(self):
"""
UserProfile.objects.get(user=self.user).delete()
response = self.client.get(self.path)
- self.assertRedirects(response, reverse('account_settings'))
+ self.assertRedirects(response, settings.ACCOUNT_MICROFRONTEND_URL, target_status_code=302)
@patch('common.djangoapps.student.views.dashboard.learner_home_mfe_enabled')
def test_redirect_to_learner_home(self, mock_learner_home_mfe_enabled):
diff --git a/common/djangoapps/student/views/dashboard.py b/common/djangoapps/student/views/dashboard.py
index f729a2aee130..05279fe8cdd3 100644
--- a/common/djangoapps/student/views/dashboard.py
+++ b/common/djangoapps/student/views/dashboard.py
@@ -518,7 +518,7 @@ def student_dashboard(request): # lint-amnesty, pylint: disable=too-many-statem
"""
user = request.user
if not UserProfile.objects.filter(user=user).exists():
- return redirect(reverse('account_settings'))
+ return redirect(settings.ACCOUNT_MICROFRONTEND_URL)
if learner_home_mfe_enabled():
return redirect(settings.LEARNER_HOME_MICROFRONTEND_URL)
@@ -623,7 +623,7 @@ def student_dashboard(request): # lint-amnesty, pylint: disable=too-many-statem
"Go to {link_start}your Account Settings{link_end}.")
).format(
link_start=HTML("").format(
- account_setting_page=reverse('account_settings'),
+ account_setting_page=settings.ACCOUNT_MICROFRONTEND_URL,
),
link_end=HTML("")
)
@@ -892,7 +892,7 @@ def student_dashboard(request): # lint-amnesty, pylint: disable=too-many-statem
except DashboardRenderStarted.RenderInvalidDashboard as exc:
response = render_to_response(exc.dashboard_template, exc.template_context)
except DashboardRenderStarted.RedirectToPage as exc:
- response = HttpResponseRedirect(exc.redirect_to or reverse('account_settings'))
+ response = HttpResponseRedirect(exc.redirect_to or settings.ACCOUNT_MICROFRONTEND_URL)
except DashboardRenderStarted.RenderCustomResponse as exc:
response = exc.response
else:
diff --git a/common/djangoapps/third_party_auth/api/tests/test_views.py b/common/djangoapps/third_party_auth/api/tests/test_views.py
index 948314a0672f..670caf04c7f1 100644
--- a/common/djangoapps/third_party_auth/api/tests/test_views.py
+++ b/common/djangoapps/third_party_auth/api/tests/test_views.py
@@ -2,10 +2,11 @@
Tests for the Third Party Auth REST API
"""
+import urllib
from unittest.mock import patch
import ddt
-import six
+from django.conf import settings
from django.http import QueryDict
from django.test.utils import override_settings
from django.urls import reverse
@@ -219,7 +220,7 @@ def make_url(self, identifier):
"""
return '?'.join([
reverse('third_party_auth_users_api_v2'),
- six.moves.urllib.parse.urlencode(identifier)
+ urllib.parse.urlencode(identifier)
])
@@ -377,11 +378,12 @@ def test_get(self):
"""
self.client.login(username=self.user.username, password=PASSWORD)
response = self.client.get(self.url, content_type="application/json")
+ next_url = urllib.parse.quote(settings.ACCOUNT_MICROFRONTEND_URL, safe="")
assert response.status_code == 200
assert (response.data ==
[{
'accepts_logins': True, 'name': 'Google',
'disconnect_url': '/auth/disconnect/google-oauth2/?',
- 'connect_url': '/auth/login/google-oauth2/?auth_entry=account_settings&next=%2Faccount%2Fsettings',
+ 'connect_url': f'/auth/login/google-oauth2/?auth_entry=account_settings&next={next_url}',
'connected': False, 'id': 'oa2-google-oauth2'
}])
diff --git a/common/djangoapps/third_party_auth/api/views.py b/common/djangoapps/third_party_auth/api/views.py
index 97d1a7d6dba4..c1127f8e335d 100644
--- a/common/djangoapps/third_party_auth/api/views.py
+++ b/common/djangoapps/third_party_auth/api/views.py
@@ -9,7 +9,6 @@
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
from django.db.models import Q
from django.http import Http404
-from django.urls import reverse
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
from rest_framework import exceptions, permissions, status, throttling
@@ -425,7 +424,7 @@ def get(self, request):
state.provider.provider_id,
pipeline.AUTH_ENTRY_ACCOUNT_SETTINGS,
# The url the user should be directed to after the auth process has completed.
- redirect_url=reverse('account_settings'),
+ redirect_url=settings.ACCOUNT_MICROFRONTEND_URL,
),
'accepts_logins': state.provider.accepts_logins,
# If the user is connected, sending a POST request to this url removes the connection
diff --git a/common/djangoapps/third_party_auth/tests/specs/base.py b/common/djangoapps/third_party_auth/tests/specs/base.py
index 8f96235017da..524cd64ff366 100644
--- a/common/djangoapps/third_party_auth/tests/specs/base.py
+++ b/common/djangoapps/third_party_auth/tests/specs/base.py
@@ -2,7 +2,6 @@
Base integration test for provider implementations.
"""
-
import json
import unittest
from contextlib import contextmanager
@@ -11,7 +10,7 @@
import pytest
from django import test
from django.conf import settings
-from django.contrib import auth
+from django.contrib import auth, messages
from django.contrib.auth import models as auth_models
from django.contrib.messages.storage import fallback
from django.contrib.sessions.backends import cache
@@ -28,7 +27,6 @@
from openedx.core.djangoapps.user_authn.views.register import RegistrationView
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
-from openedx.core.djangoapps.user_api.accounts.settings_views import account_settings_context
from common.djangoapps.student import models as student_models
from common.djangoapps.student.tests.factories import UserFactory
@@ -56,9 +54,9 @@ def _check_registration_form_username(self, form_data, test_username, expected):
test_username (str): username to check the form initialization with.
expected (str): expected cleaned username after the form initialization.
"""
- form_data['username'] = test_username
+ form_data["username"] = test_username
form_field_data = self.provider.get_register_form_data(form_data)
- assert form_field_data['username'] == expected
+ assert form_field_data["username"] == expected
def assert_redirect_to_provider_looks_correct(self, response):
"""Asserts the redirect to the provider's site looks correct.
@@ -70,9 +68,11 @@ def assert_redirect_to_provider_looks_correct(self, response):
example, more details about the format of the Location header.
"""
assert 302 == response.status_code
- assert response.has_header('Location')
+ assert response.has_header("Location")
- def assert_register_response_in_pipeline_looks_correct(self, response, pipeline_kwargs, required_fields): # lint-amnesty, pylint: disable=invalid-name
+ def assert_register_response_in_pipeline_looks_correct(
+ self, response, pipeline_kwargs, required_fields
+ ): # lint-amnesty, pylint: disable=invalid-name
"""Performs spot checks of the rendered register.html page.
When we display the new account registration form after the user signs
@@ -84,10 +84,7 @@ def assert_register_response_in_pipeline_looks_correct(self, response, pipeline_
assertions in your test, override this method.
"""
# Check that the correct provider was selected.
- self.assertContains(
- response,
- '"errorMessage": null'
- )
+ self.assertContains(response, '"errorMessage": null')
self.assertContains(
response,
f'"currentProvider": "{self.provider.name}"',
@@ -99,45 +96,67 @@ def assert_register_response_in_pipeline_looks_correct(self, response, pipeline_
if prepopulated_form_data in required_fields:
self.assertContains(response, form_field_data[prepopulated_form_data])
- def assert_register_form_populates_unicode_username_correctly(self, request): # lint-amnesty, pylint: disable=invalid-name
+ def _get_user_providers_state(self, request):
"""
- Check the registration form username field behaviour with unicode values.
-
- The field could be empty or prefilled depending on whether ENABLE_UNICODE_USERNAME feature is disabled/enabled.
+ Return provider user states and duplicated providers.
"""
- unicode_username = 'Червона_Калина'
- ascii_substring = 'untouchable'
- partial_unicode_username = unicode_username + ascii_substring
- pipeline_kwargs = pipeline.get(request)['kwargs']
-
- assert settings.FEATURES['ENABLE_UNICODE_USERNAME'] is False
-
- self._check_registration_form_username(pipeline_kwargs, unicode_username, '')
- self._check_registration_form_username(pipeline_kwargs, partial_unicode_username, ascii_substring)
-
- with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_UNICODE_USERNAME': True}):
- self._check_registration_form_username(pipeline_kwargs, unicode_username, unicode_username)
-
- # pylint: disable=invalid-name
- def assert_account_settings_context_looks_correct(self, context, duplicate=False, linked=None):
- """Asserts the user's account settings page context is in the expected state.
+ data = {
+ "auth": {},
+ }
+ data["duplicate_provider"] = pipeline.get_duplicate_provider(messages.get_messages(request))
+ auth_states = pipeline.get_provider_user_states(request.user)
+ data["auth"]["providers"] = [
+ {
+ "name": state.provider.name,
+ "connected": state.has_account,
+ }
+ for state in auth_states
+ if state.provider.display_for_login or state.has_account
+ ]
+ return data
+
+ def assert_third_party_accounts_state(self, request, duplicate=False, linked=None):
+ """
+ Asserts the user's third party account in the expected state.
- If duplicate is True, we expect context['duplicate_provider'] to contain
+ If duplicate is True, we expect data['duplicate_provider'] to contain
the duplicate provider backend name. If linked is passed, we conditionally
- check that the provider is included in context['auth']['providers'] and
+ check that the provider is included in data['auth']['providers'] and
its connected state is correct.
"""
+ data = self._get_user_providers_state(request)
if duplicate:
- assert context['duplicate_provider'] == self.provider.backend_name
+ assert data["duplicate_provider"] == self.provider.backend_name
else:
- assert context['duplicate_provider'] is None
+ assert data["duplicate_provider"] is None
if linked is not None:
expected_provider = [
- provider for provider in context['auth']['providers'] if provider['name'] == self.provider.name
+ provider for provider in data["auth"]["providers"] if provider["name"] == self.provider.name
][0]
assert expected_provider is not None
- assert expected_provider['connected'] == linked
+ assert expected_provider["connected"] == linked
+
+ def assert_register_form_populates_unicode_username_correctly(
+ self, request
+ ): # lint-amnesty, pylint: disable=invalid-name
+ """
+ Check the registration form username field behaviour with unicode values.
+
+ The field could be empty or prefilled depending on whether ENABLE_UNICODE_USERNAME feature is disabled/enabled.
+ """
+ unicode_username = "Червона_Калина"
+ ascii_substring = "untouchable"
+ partial_unicode_username = unicode_username + ascii_substring
+ pipeline_kwargs = pipeline.get(request)["kwargs"]
+
+ assert settings.FEATURES["ENABLE_UNICODE_USERNAME"] is False
+
+ self._check_registration_form_username(pipeline_kwargs, unicode_username, "")
+ self._check_registration_form_username(pipeline_kwargs, partial_unicode_username, ascii_substring)
+
+ with mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_UNICODE_USERNAME": True}):
+ self._check_registration_form_username(pipeline_kwargs, unicode_username, unicode_username)
def assert_exception_redirect_looks_correct(self, expected_uri, auth_entry=None):
"""Tests middleware conditional redirection.
@@ -147,49 +166,48 @@ def assert_exception_redirect_looks_correct(self, expected_uri, auth_entry=None)
"""
exception_middleware = middleware.ExceptionMiddleware(get_response=lambda request: None)
request, _ = self.get_request_and_strategy(auth_entry=auth_entry)
- response = exception_middleware.process_exception(
- request, exceptions.AuthCanceled(request.backend))
- location = response.get('Location')
+ response = exception_middleware.process_exception(request, exceptions.AuthCanceled(request.backend))
+ location = response.get("Location")
assert 302 == response.status_code
- assert 'canceled' in location
+ assert "canceled" in location
assert self.backend_name in location
- assert location.startswith(expected_uri + '?')
+ assert location.startswith(expected_uri + "?")
def assert_json_failure_response_is_inactive_account(self, response):
"""Asserts failure on /login for inactive account looks right."""
assert 400 == response.status_code
- payload = json.loads(response.content.decode('utf-8'))
+ payload = json.loads(response.content.decode("utf-8"))
context = {
- 'platformName': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME),
- 'supportLink': configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK)
+ "platformName": configuration_helpers.get_value("PLATFORM_NAME", settings.PLATFORM_NAME),
+ "supportLink": configuration_helpers.get_value("SUPPORT_SITE_LINK", settings.SUPPORT_SITE_LINK),
}
- assert not payload.get('success')
- assert 'inactive-user' in payload.get('error_code')
- assert context == payload.get('context')
+ assert not payload.get("success")
+ assert "inactive-user" in payload.get("error_code")
+ assert context == payload.get("context")
def assert_json_failure_response_is_missing_social_auth(self, response):
"""Asserts failure on /login for missing social auth looks right."""
assert 403 == response.status_code
- payload = json.loads(response.content.decode('utf-8'))
- assert not payload.get('success')
- assert payload.get('error_code') == 'third-party-auth-with-no-linked-account'
+ payload = json.loads(response.content.decode("utf-8"))
+ assert not payload.get("success")
+ assert payload.get("error_code") == "third-party-auth-with-no-linked-account"
def assert_json_failure_response_is_username_collision(self, response):
"""Asserts the json response indicates a username collision."""
assert 409 == response.status_code
- payload = json.loads(response.content.decode('utf-8'))
- assert not payload.get('success')
- assert 'It looks like this username is already taken' == payload['username'][0]['user_message']
+ payload = json.loads(response.content.decode("utf-8"))
+ assert not payload.get("success")
+ assert "It looks like this username is already taken" == payload["username"][0]["user_message"]
def assert_json_success_response_looks_correct(self, response, verify_redirect_url):
"""Asserts the json response indicates success and redirection."""
assert 200 == response.status_code
- payload = json.loads(response.content.decode('utf-8'))
- assert payload.get('success')
+ payload = json.loads(response.content.decode("utf-8"))
+ assert payload.get("success")
if verify_redirect_url:
- assert pipeline.get_complete_url(self.provider.backend_name) == payload.get('redirect_url')
+ assert pipeline.get_complete_url(self.provider.backend_name) == payload.get("redirect_url")
def assert_login_response_before_pipeline_looks_correct(self, response):
"""Asserts a GET of /login not in the pipeline looks correct."""
@@ -218,19 +236,19 @@ def assert_redirect_after_pipeline_completes(self, response, expected_redirect_u
assert 302 == response.status_code
# NOTE: Ideally we should use assertRedirects(), however it errors out due to the hostname, testserver,
# not being properly set. This may be an issue with the call made by PSA, but we are not certain.
- assert response.get('Location').endswith(
+ assert response.get("Location").endswith(
expected_redirect_url or django_settings.SOCIAL_AUTH_LOGIN_REDIRECT_URL
)
def assert_redirect_to_login_looks_correct(self, response):
"""Asserts a response would redirect to /login."""
assert 302 == response.status_code
- assert '/login' == response.get('Location')
+ assert "/login" == response.get("Location")
def assert_redirect_to_register_looks_correct(self, response):
"""Asserts a response would redirect to /register."""
assert 302 == response.status_code
- assert '/register' == response.get('Location')
+ assert "/register" == response.get("Location")
def assert_register_response_before_pipeline_looks_correct(self, response):
"""Asserts a GET of /register not in the pipeline looks correct."""
@@ -241,43 +259,41 @@ def assert_register_response_before_pipeline_looks_correct(self, response):
def assert_social_auth_does_not_exist_for_user(self, user, strategy):
"""Asserts a user does not have an auth with the expected provider."""
- social_auths = strategy.storage.user.get_social_auth_for_user(
- user, provider=self.provider.backend_name)
+ social_auths = strategy.storage.user.get_social_auth_for_user(user, provider=self.provider.backend_name)
assert 0 == len(social_auths)
def assert_social_auth_exists_for_user(self, user, strategy):
"""Asserts a user has a social auth with the expected provider."""
- social_auths = strategy.storage.user.get_social_auth_for_user(
- user, provider=self.provider.backend_name)
+ social_auths = strategy.storage.user.get_social_auth_for_user(user, provider=self.provider.backend_name)
assert 1 == len(social_auths)
assert self.backend_name == social_auths[0].provider
def assert_logged_in_cookie_redirect(self, response):
- """Verify that the user was redirected in order to set the logged in cookie. """
+ """Verify that the user was redirected in order to set the logged in cookie."""
assert response.status_code == 302
- assert response['Location'] == pipeline.get_complete_url(self.provider.backend_name)
- assert response.cookies[django_settings.EDXMKTG_LOGGED_IN_COOKIE_NAME].value == 'true'
+ assert response["Location"] == pipeline.get_complete_url(self.provider.backend_name)
+ assert response.cookies[django_settings.EDXMKTG_LOGGED_IN_COOKIE_NAME].value == "true"
assert django_settings.EDXMKTG_USER_INFO_COOKIE_NAME in response.cookies
@property
def backend_name(self):
- """ Shortcut for the backend name """
+ """Shortcut for the backend name"""
return self.provider.backend_name
def get_registration_post_vars(self, overrides=None):
"""POST vars generated by the registration form."""
defaults = {
- 'username': 'username',
- 'name': 'First Last',
- 'gender': '',
- 'year_of_birth': '',
- 'level_of_education': '',
- 'goals': '',
- 'honor_code': 'true',
- 'terms_of_service': 'true',
- 'password': 'password',
- 'mailing_address': '',
- 'email': 'user@email.com',
+ "username": "username",
+ "name": "First Last",
+ "gender": "",
+ "year_of_birth": "",
+ "level_of_education": "",
+ "goals": "",
+ "honor_code": "true",
+ "terms_of_service": "true",
+ "password": "password",
+ "mailing_address": "",
+ "email": "user@email.com",
}
if overrides:
@@ -294,12 +310,13 @@ def get_request_and_strategy(self, auth_entry=None, redirect_uri=None):
social_django.utils.strategy().
"""
request = self.request_factory.get(
- pipeline.get_complete_url(self.backend_name) +
- '?redirect_state=redirect_state_value&code=code_value&state=state_value')
+ pipeline.get_complete_url(self.backend_name)
+ + "?redirect_state=redirect_state_value&code=code_value&state=state_value"
+ )
request.site = SiteFactory.create()
request.user = auth_models.AnonymousUser()
request.session = cache.SessionStore()
- request.session[self.backend_name + '_state'] = 'state_value'
+ request.session[self.backend_name + "_state"] = "state_value"
if auth_entry:
request.session[pipeline.AUTH_ENTRY_KEY] = auth_entry
@@ -312,7 +329,7 @@ def get_request_and_strategy(self, auth_entry=None, redirect_uri=None):
def _get_login_post_request(self, strategy):
"""Gets a fully-configured login POST request given a strategy and pipeline."""
- request = self.request_factory.post(reverse('login_api'))
+ request = self.request_factory.post(reverse("login_api"))
# Note: The shared GET request can't be used for login, which is now POST-only,
# so this POST request is given a copy of all configuration from the GET request
@@ -329,7 +346,7 @@ def _get_login_post_request(self, strategy):
def _patch_edxmako_current_request(self, request):
"""Make ``request`` be the current request for edxmako template rendering."""
- with mock.patch('common.djangoapps.edxmako.request_context.get_current_request', return_value=request):
+ with mock.patch("common.djangoapps.edxmako.request_context.get_current_request", return_value=request):
yield
def get_user_by_email(self, strategy, email):
@@ -337,11 +354,13 @@ def get_user_by_email(self, strategy, email):
return strategy.storage.user.user_model().objects.get(email=email)
def set_logged_in_cookies(self, request):
- """Simulate setting the marketing site cookie on the request. """
- request.COOKIES[django_settings.EDXMKTG_LOGGED_IN_COOKIE_NAME] = 'true'
- request.COOKIES[django_settings.EDXMKTG_USER_INFO_COOKIE_NAME] = json.dumps({
- 'version': django_settings.EDXMKTG_USER_INFO_COOKIE_VERSION,
- })
+ """Simulate setting the marketing site cookie on the request."""
+ request.COOKIES[django_settings.EDXMKTG_LOGGED_IN_COOKIE_NAME] = "true"
+ request.COOKIES[django_settings.EDXMKTG_USER_INFO_COOKIE_NAME] = json.dumps(
+ {
+ "version": django_settings.EDXMKTG_USER_INFO_COOKIE_VERSION,
+ }
+ )
def create_user_models_for_existing_account(self, strategy, email, password, username, skip_social_auth=False):
"""Creates user, profile, registration, and (usually) social auth.
@@ -371,10 +390,10 @@ def fake_auth_complete(self, strategy):
"""
args = ()
kwargs = {
- 'request': strategy.request,
- 'backend': strategy.request.backend,
- 'user': None,
- 'response': self.get_response_data(),
+ "request": strategy.request,
+ "backend": strategy.request.backend,
+ "user": None,
+ "response": self.get_response_data(),
}
return strategy.authenticate(*args, **kwargs)
@@ -386,6 +405,7 @@ class IntegrationTestMixin(testutil.TestCase, test.TestCase, HelperMixin):
currently less comprehensive. Some providers are tested with this, others with
IntegrationTest.
"""
+
# Provider information:
PROVIDER_NAME = "override"
PROVIDER_BACKEND = "override"
@@ -399,8 +419,8 @@ def setUp(self):
super().setUp()
self.request_factory = test.RequestFactory()
- self.login_page_url = reverse('signin_user')
- self.register_page_url = reverse('register_user')
+ self.login_page_url = reverse("signin_user")
+ self.register_page_url = reverse("register_user")
patcher = testutil.patch_mako_templates()
patcher.start()
self.addCleanup(patcher.stop)
@@ -415,47 +435,44 @@ def _test_register(self, **extra_defaults):
try_login_response = self.client.get(provider_register_url)
# The user should be redirected to the provider's login page:
assert try_login_response.status_code == 302
- provider_response = self.do_provider_login(try_login_response['Location'])
+ provider_response = self.do_provider_login(try_login_response["Location"])
# We should be redirected to the register screen since this account is not linked to an edX account:
assert provider_response.status_code == 302
- assert provider_response['Location'] == self.register_page_url
+ assert provider_response["Location"] == self.register_page_url
register_response = self.client.get(self.register_page_url)
tpa_context = register_response.context["data"]["third_party_auth"]
- assert tpa_context['errorMessage'] is None
+ assert tpa_context["errorMessage"] is None
# Check that the "You've successfully signed into [PROVIDER_NAME]" message is shown.
- assert tpa_context['currentProvider'] == self.PROVIDER_NAME
+ assert tpa_context["currentProvider"] == self.PROVIDER_NAME
# Check that the data (e.g. email) from the provider is displayed in the form:
- form_data = register_response.context['data']['registration_form_desc']
- form_fields = {field['name']: field for field in form_data['fields']}
- assert form_fields['email']['defaultValue'] == self.USER_EMAIL
- assert form_fields['name']['defaultValue'] == self.USER_NAME
- assert form_fields['username']['defaultValue'] == self.USER_USERNAME
+ form_data = register_response.context["data"]["registration_form_desc"]
+ form_fields = {field["name"]: field for field in form_data["fields"]}
+ assert form_fields["email"]["defaultValue"] == self.USER_EMAIL
+ assert form_fields["name"]["defaultValue"] == self.USER_NAME
+ assert form_fields["username"]["defaultValue"] == self.USER_USERNAME
for field_name, value in extra_defaults.items():
- assert form_fields[field_name]['defaultValue'] == value
+ assert form_fields[field_name]["defaultValue"] == value
registration_values = {
- 'email': 'email-edited@tpa-test.none',
- 'name': 'My Customized Name',
- 'username': 'new_username',
- 'honor_code': True,
+ "email": "email-edited@tpa-test.none",
+ "name": "My Customized Name",
+ "username": "new_username",
+ "honor_code": True,
}
# Now complete the form:
- ajax_register_response = self.client.post(
- reverse('user_api_registration'),
- registration_values
- )
+ ajax_register_response = self.client.post(reverse("user_api_registration"), registration_values)
assert ajax_register_response.status_code == 200
# Then the AJAX will finish the third party auth:
continue_response = self.client.get(tpa_context["finishAuthUrl"])
# And we should be redirected to the dashboard:
assert continue_response.status_code == 302
- assert continue_response['Location'] == reverse('dashboard')
+ assert continue_response["Location"] == reverse("dashboard")
# Now check that we can login again, whether or not we have yet verified the account:
self.client.logout()
self._test_return_login(user_is_activated=False)
self.client.logout()
- self.verify_user_email('email-edited@tpa-test.none')
+ self.verify_user_email("email-edited@tpa-test.none")
self._test_return_login(user_is_activated=True)
def _test_login(self):
@@ -468,27 +485,27 @@ def _test_login(self):
try_login_response = self.client.get(provider_login_url)
# The user should be redirected to the provider's login page:
assert try_login_response.status_code == 302
- complete_response = self.do_provider_login(try_login_response['Location'])
+ complete_response = self.do_provider_login(try_login_response["Location"])
# We should be redirected to the login screen since this account is not linked to an edX account:
assert complete_response.status_code == 302
- assert complete_response['Location'] == self.login_page_url
+ assert complete_response["Location"] == self.login_page_url
login_response = self.client.get(self.login_page_url)
tpa_context = login_response.context["data"]["third_party_auth"]
- assert tpa_context['errorMessage'] is None
+ assert tpa_context["errorMessage"] is None
# Check that the "You've successfully signed into [PROVIDER_NAME]" message is shown.
- assert tpa_context['currentProvider'] == self.PROVIDER_NAME
+ assert tpa_context["currentProvider"] == self.PROVIDER_NAME
# Now the user enters their username and password.
# The AJAX on the page will log them in:
ajax_login_response = self.client.post(
- reverse('user_api_login_session', kwargs={'api_version': 'v1'}),
- {'email': self.user.email, 'password': 'Password1234'}
+ reverse("user_api_login_session", kwargs={"api_version": "v1"}),
+ {"email": self.user.email, "password": "Password1234"},
)
assert ajax_login_response.status_code == 200
# Then the AJAX will finish the third party auth:
continue_response = self.client.get(tpa_context["finishAuthUrl"])
# And we should be redirected to the dashboard:
assert continue_response.status_code == 302
- assert continue_response['Location'] == reverse('dashboard')
+ assert continue_response["Location"] == reverse("dashboard")
# Now check that we can login again:
self.client.logout()
@@ -502,9 +519,9 @@ def do_provider_login(self, provider_redirect_url):
raise NotImplementedError
def _test_return_login(self, user_is_activated=True, previous_session_timed_out=False):
- """ Test logging in to an account that is already linked. """
+ """Test logging in to an account that is already linked."""
# Make sure we're not logged in:
- dashboard_response = self.client.get(reverse('dashboard'))
+ dashboard_response = self.client.get(reverse("dashboard"))
assert dashboard_response.status_code == 302
# The user goes to the login page, and sees a button to login with this provider:
provider_login_url = self._check_login_page()
@@ -512,22 +529,22 @@ def _test_return_login(self, user_is_activated=True, previous_session_timed_out=
try_login_response = self.client.get(provider_login_url)
# The user should be redirected to the provider:
assert try_login_response.status_code == 302
- login_response = self.do_provider_login(try_login_response['Location'])
+ login_response = self.do_provider_login(try_login_response["Location"])
# If the previous session was manually logged out, there will be one weird redirect
# required to set the login cookie (it sticks around if the main session times out):
if not previous_session_timed_out:
assert login_response.status_code == 302
- assert login_response['Location'] == (self.complete_url + '?')
+ assert login_response["Location"] == (self.complete_url + "?")
# And then we should be redirected to the dashboard:
- login_response = self.client.get(login_response['Location'])
+ login_response = self.client.get(login_response["Location"])
assert login_response.status_code == 302
if user_is_activated:
- url_expected = reverse('dashboard')
+ url_expected = reverse("dashboard")
else:
- url_expected = reverse('third_party_inactive_redirect') + '?next=' + reverse('dashboard')
- assert login_response['Location'] == url_expected
+ url_expected = reverse("third_party_inactive_redirect") + "?next=" + reverse("dashboard")
+ assert login_response["Location"] == url_expected
# Now we are logged in:
- dashboard_response = self.client.get(reverse('dashboard'))
+ dashboard_response = self.client.get(reverse("dashboard"))
assert dashboard_response.status_code == 200
def _check_login_page(self):
@@ -545,22 +562,23 @@ def _check_register_page(self):
return self._check_login_or_register_page(self.register_page_url, "registerUrl")
def _check_login_or_register_page(self, url, url_to_return):
- """ Shared logic for _check_login_page() and _check_register_page() """
+ """Shared logic for _check_login_page() and _check_register_page()"""
response = self.client.get(url)
self.assertContains(response, self.PROVIDER_NAME)
- context_data = response.context['data']['third_party_auth']
- provider_urls = {provider['id']: provider[url_to_return] for provider in context_data['providers']}
+ context_data = response.context["data"]["third_party_auth"]
+ provider_urls = {provider["id"]: provider[url_to_return] for provider in context_data["providers"]}
assert self.PROVIDER_ID in provider_urls
return provider_urls[self.PROVIDER_ID]
@property
def complete_url(self):
- """ Get the auth completion URL for this provider """
- return reverse('social:complete', kwargs={'backend': self.PROVIDER_BACKEND})
+ """Get the auth completion URL for this provider"""
+ return reverse("social:complete", kwargs={"backend": self.PROVIDER_BACKEND})
@unittest.skipUnless(
- testutil.AUTH_FEATURES_KEY in django_settings.FEATURES, testutil.AUTH_FEATURES_KEY + ' not in settings.FEATURES')
+ testutil.AUTH_FEATURES_KEY in django_settings.FEATURES, testutil.AUTH_FEATURES_KEY + " not in settings.FEATURES"
+)
@django_utils.override_settings() # For settings reversion on a method-by-method basis.
class IntegrationTest(testutil.TestCase, test.TestCase, HelperMixin):
"""Abstract base class for provider integration tests."""
@@ -572,46 +590,51 @@ def setUp(self):
# Actual tests, executed once per child.
def test_canceling_authentication_redirects_to_login_when_auth_entry_login(self):
- self.assert_exception_redirect_looks_correct('/login', auth_entry=pipeline.AUTH_ENTRY_LOGIN)
+ self.assert_exception_redirect_looks_correct("/login", auth_entry=pipeline.AUTH_ENTRY_LOGIN)
def test_canceling_authentication_redirects_to_register_when_auth_entry_register(self):
- self.assert_exception_redirect_looks_correct('/register', auth_entry=pipeline.AUTH_ENTRY_REGISTER)
+ self.assert_exception_redirect_looks_correct("/register", auth_entry=pipeline.AUTH_ENTRY_REGISTER)
def test_canceling_authentication_redirects_to_account_settings_when_auth_entry_account_settings(self):
self.assert_exception_redirect_looks_correct(
- '/account/settings', auth_entry=pipeline.AUTH_ENTRY_ACCOUNT_SETTINGS
+ "/account/settings", auth_entry=pipeline.AUTH_ENTRY_ACCOUNT_SETTINGS
)
def test_canceling_authentication_redirects_to_root_when_auth_entry_not_set(self):
- self.assert_exception_redirect_looks_correct('/')
+ self.assert_exception_redirect_looks_correct("/")
- @mock.patch('common.djangoapps.third_party_auth.pipeline.segment.track')
+ @mock.patch("common.djangoapps.third_party_auth.pipeline.segment.track")
def test_full_pipeline_succeeds_for_linking_account(self, _mock_segment_track):
# First, create, the GET request and strategy that store pipeline state,
# configure the backend, and mock out wire traffic.
get_request, strategy = self.get_request_and_strategy(
- auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
+ auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri="social:complete"
+ )
get_request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
get_request.user = self.create_user_models_for_existing_account(
- strategy, 'user@example.com', 'password', self.get_username(), skip_social_auth=True)
- partial_pipeline_token = strategy.session_get('partial_pipeline_token')
+ strategy, "user@example.com", "password", self.get_username(), skip_social_auth=True
+ )
+ partial_pipeline_token = strategy.session_get("partial_pipeline_token")
partial_data = strategy.storage.partial.load(partial_pipeline_token)
# Instrument the pipeline to get to the dashboard with the full
# expected state.
- self.client.get(
- pipeline.get_login_url(self.provider.provider_id, pipeline.AUTH_ENTRY_LOGIN))
- actions.do_complete(get_request.backend, social_views._do_login, # pylint: disable=protected-access
- request=get_request)
+ self.client.get(pipeline.get_login_url(self.provider.provider_id, pipeline.AUTH_ENTRY_LOGIN))
+ actions.do_complete(
+ get_request.backend, social_views._do_login, request=get_request # pylint: disable=protected-access
+ )
post_request = self._get_login_post_request(strategy)
login_user(post_request)
- actions.do_complete(post_request.backend, social_views._do_login, # pylint: disable=protected-access, no-member
- request=post_request)
+ actions.do_complete(
+ post_request.backend,
+ social_views._do_login, # pylint: disable=protected-access, no-member
+ request=post_request,
+ )
# First we expect that we're in the unlinked state, and that there
# really is no association in the backend.
- self.assert_account_settings_context_looks_correct(account_settings_context(get_request), linked=False)
+ self.assert_third_party_accounts_state(get_request, linked=False)
self.assert_social_auth_does_not_exist_for_user(get_request.user, strategy)
# We should be redirected back to the complete page, setting
@@ -630,16 +653,18 @@ def test_full_pipeline_succeeds_for_linking_account(self, _mock_segment_track):
# Now we expect to be in the linked state, with a backend entry.
self.assert_social_auth_exists_for_user(get_request.user, strategy)
- self.assert_account_settings_context_looks_correct(account_settings_context(get_request), linked=True)
+ self.assert_third_party_accounts_state(get_request, linked=True)
def test_full_pipeline_succeeds_for_unlinking_account(self):
# First, create, the GET request and strategy that store pipeline state,
# configure the backend, and mock out wire traffic.
get_request, strategy = self.get_request_and_strategy(
- auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
+ auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri="social:complete"
+ )
get_request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
user = self.create_user_models_for_existing_account(
- strategy, 'user@example.com', 'password', self.get_username())
+ strategy, "user@example.com", "password", self.get_username()
+ )
self.assert_social_auth_exists_for_user(user, strategy)
# We're already logged in, so simulate that the cookie is set correctly
@@ -647,36 +672,37 @@ def test_full_pipeline_succeeds_for_unlinking_account(self):
# Instrument the pipeline to get to the dashboard with the full
# expected state.
- self.client.get(
- pipeline.get_login_url(self.provider.provider_id, pipeline.AUTH_ENTRY_LOGIN))
- actions.do_complete(get_request.backend, social_views._do_login, # pylint: disable=protected-access
- request=get_request)
+ self.client.get(pipeline.get_login_url(self.provider.provider_id, pipeline.AUTH_ENTRY_LOGIN))
+ actions.do_complete(
+ get_request.backend, social_views._do_login, request=get_request # pylint: disable=protected-access
+ )
post_request = self._get_login_post_request(strategy)
with self._patch_edxmako_current_request(post_request):
login_user(post_request)
- actions.do_complete(post_request.backend, social_views._do_login, user=user, # pylint: disable=protected-access, no-member
- request=post_request)
+ actions.do_complete(
+ post_request.backend,
+ social_views._do_login, # pylint: disable=protected-access
+ user=user, # pylint: disable=no-member
+ request=post_request,
+ )
# Copy the user that was set on the post_request object back to the original get_request object.
get_request.user = post_request.user
# First we expect that we're in the linked state, with a backend entry.
- self.assert_account_settings_context_looks_correct(account_settings_context(get_request), linked=True)
+ self.assert_third_party_accounts_state(get_request, linked=True)
self.assert_social_auth_exists_for_user(get_request.user, strategy)
# Fire off the disconnect pipeline to unlink.
self.assert_redirect_after_pipeline_completes(
actions.do_disconnect(
- get_request.backend,
- get_request.user,
- None,
- redirect_field_name=auth.REDIRECT_FIELD_NAME
+ get_request.backend, get_request.user, None, redirect_field_name=auth.REDIRECT_FIELD_NAME
)
)
# Now we expect to be in the unlinked state, with no backend entry.
- self.assert_account_settings_context_looks_correct(account_settings_context(get_request), linked=False)
+ self.assert_third_party_accounts_state(get_request, linked=False)
self.assert_social_auth_does_not_exist_for_user(user, strategy)
def test_linking_already_associated_account_raises_auth_already_associated(self):
@@ -684,16 +710,18 @@ def test_linking_already_associated_account_raises_auth_already_associated(self)
# test_already_associated_exception_populates_dashboard_with_error. It
# verifies the exception gets raised when we expect; the latter test
# covers exception handling.
- email = 'user@example.com'
- password = 'password'
+ email = "user@example.com"
+ password = "password"
username = self.get_username()
_, strategy = self.get_request_and_strategy(
- auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
+ auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri="social:complete"
+ )
backend = strategy.request.backend
backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
linked_user = self.create_user_models_for_existing_account(strategy, email, password, username)
unlinked_user = social_utils.Storage.user.create_user(
- email='other_' + email, password=password, username='other_' + username)
+ email="other_" + email, password=password, username="other_" + username
+ )
self.assert_social_auth_exists_for_user(linked_user, strategy)
self.assert_social_auth_does_not_exist_for_user(unlinked_user, strategy)
@@ -711,42 +739,50 @@ def test_already_associated_exception_populates_dashboard_with_error(self):
# covered in other tests. Using linked=True does, however, let us test
# that the duplicate error has no effect on the state of the controls.
get_request, strategy = self.get_request_and_strategy(
- auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
+ auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri="social:complete"
+ )
strategy.request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
user = self.create_user_models_for_existing_account(
- strategy, 'user@example.com', 'password', self.get_username())
+ strategy, "user@example.com", "password", self.get_username()
+ )
self.assert_social_auth_exists_for_user(user, strategy)
- self.client.get('/login')
+ self.client.get("/login")
self.client.get(pipeline.get_login_url(self.provider.provider_id, pipeline.AUTH_ENTRY_LOGIN))
- actions.do_complete(get_request.backend, social_views._do_login, # pylint: disable=protected-access
- request=get_request)
+ actions.do_complete(
+ get_request.backend, social_views._do_login, request=get_request # pylint: disable=protected-access
+ )
post_request = self._get_login_post_request(strategy)
with self._patch_edxmako_current_request(post_request):
login_user(post_request)
- actions.do_complete(post_request.backend, social_views._do_login, # pylint: disable=protected-access, no-member
- user=user, request=post_request)
+ actions.do_complete(
+ post_request.backend,
+ social_views._do_login, # pylint: disable=protected-access, no-member
+ user=user,
+ request=post_request,
+ )
# Monkey-patch storage for messaging; pylint: disable=protected-access
post_request._messages = fallback.FallbackStorage(post_request)
middleware.ExceptionMiddleware(get_response=lambda request: None).process_exception(
- post_request,
- exceptions.AuthAlreadyAssociated(self.provider.backend_name, 'account is already in use.'))
+ post_request, exceptions.AuthAlreadyAssociated(self.provider.backend_name, "account is already in use.")
+ )
- self.assert_account_settings_context_looks_correct(
- account_settings_context(post_request), duplicate=True, linked=True)
+ self.assert_third_party_accounts_state(post_request, duplicate=True, linked=True)
- @mock.patch('common.djangoapps.third_party_auth.pipeline.segment.track')
+ @mock.patch("common.djangoapps.third_party_auth.pipeline.segment.track")
def test_full_pipeline_succeeds_for_signing_in_to_existing_active_account(self, _mock_segment_track):
# First, create, the GET request and strategy that store pipeline state,
# configure the backend, and mock out wire traffic.
get_request, strategy = self.get_request_and_strategy(
- auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
+ auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri="social:complete"
+ )
strategy.request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
user = self.create_user_models_for_existing_account(
- strategy, 'user@example.com', 'password', self.get_username())
- partial_pipeline_token = strategy.session_get('partial_pipeline_token')
+ strategy, "user@example.com", "password", self.get_username()
+ )
+ partial_pipeline_token = strategy.session_get("partial_pipeline_token")
partial_data = strategy.storage.partial.load(partial_pipeline_token)
self.assert_social_auth_exists_for_user(user, strategy)
@@ -754,19 +790,21 @@ def test_full_pipeline_succeeds_for_signing_in_to_existing_active_account(self,
# Begin! Ensure that the login form contains expected controls before
# the user starts the pipeline.
- self.assert_login_response_before_pipeline_looks_correct(self.client.get('/login'))
+ self.assert_login_response_before_pipeline_looks_correct(self.client.get("/login"))
# The pipeline starts by a user GETting /auth/login/.
# Synthesize that request and check that it redirects to the correct
# provider page.
- self.assert_redirect_to_provider_looks_correct(self.client.get(
- pipeline.get_login_url(self.provider.provider_id, pipeline.AUTH_ENTRY_LOGIN)))
+ self.assert_redirect_to_provider_looks_correct(
+ self.client.get(pipeline.get_login_url(self.provider.provider_id, pipeline.AUTH_ENTRY_LOGIN))
+ )
# Next, the provider makes a request against /auth/complete/
# to resume the pipeline.
# pylint: disable=protected-access
- self.assert_redirect_to_login_looks_correct(actions.do_complete(get_request.backend, social_views._do_login,
- request=get_request))
+ self.assert_redirect_to_login_looks_correct(
+ actions.do_complete(get_request.backend, social_views._do_login, request=get_request)
+ )
# At this point we know the pipeline has resumed correctly. Next we
# fire off the view that displays the login form and posts it via JS.
@@ -781,10 +819,16 @@ def test_full_pipeline_succeeds_for_signing_in_to_existing_active_account(self,
# We should be redirected back to the complete page, setting
# the "logged in" cookie for the marketing site.
- self.assert_logged_in_cookie_redirect(actions.do_complete(
- post_request.backend, social_views._do_login, post_request.user, None, # pylint: disable=protected-access, no-member
- redirect_field_name=auth.REDIRECT_FIELD_NAME, request=post_request
- ))
+ self.assert_logged_in_cookie_redirect(
+ actions.do_complete(
+ post_request.backend,
+ social_views._do_login,
+ post_request.user,
+ None, # pylint: disable=protected-access, no-member
+ redirect_field_name=auth.REDIRECT_FIELD_NAME,
+ request=post_request,
+ )
+ )
# Set the cookie and try again
self.set_logged_in_cookies(get_request)
@@ -795,14 +839,16 @@ def test_full_pipeline_succeeds_for_signing_in_to_existing_active_account(self,
self.assert_redirect_after_pipeline_completes(
self.do_complete(strategy, get_request, partial_pipeline_token, partial_data, user)
)
- self.assert_account_settings_context_looks_correct(account_settings_context(get_request))
+ self.assert_third_party_accounts_state(get_request)
def test_signin_fails_if_account_not_active(self):
_, strategy = self.get_request_and_strategy(
- auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
+ auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri="social:complete"
+ )
strategy.request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
- user = self.create_user_models_for_existing_account(strategy, 'user@example.com', 'password',
- self.get_username())
+ user = self.create_user_models_for_existing_account(
+ strategy, "user@example.com", "password", self.get_username()
+ )
user.is_active = False
user.save()
@@ -813,25 +859,28 @@ def test_signin_fails_if_account_not_active(self):
def test_signin_fails_if_no_account_associated(self):
_, strategy = self.get_request_and_strategy(
- auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
+ auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri="social:complete"
+ )
strategy.request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
self.create_user_models_for_existing_account(
- strategy, 'user@example.com', 'password', self.get_username(), skip_social_auth=True)
+ strategy, "user@example.com", "password", self.get_username(), skip_social_auth=True
+ )
post_request = self._get_login_post_request(strategy)
self.assert_json_failure_response_is_missing_social_auth(login_user(post_request))
def test_signin_associates_user_if_oauth_provider_and_tpa_is_required(self):
- username, email, password = self.get_username(), 'user@example.com', 'password'
+ username, email, password = self.get_username(), "user@example.com", "password"
_, strategy = self.get_request_and_strategy(
- auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
+ auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri="social:complete"
+ )
user = self.create_user_models_for_existing_account(strategy, email, password, username, skip_social_auth=True)
with mock.patch(
- 'common.djangoapps.third_party_auth.pipeline.get_associated_user_by_email_response',
- return_value=[{'user': user}, True],
+ "common.djangoapps.third_party_auth.pipeline.get_associated_user_by_email_response",
+ return_value=[{"user": user}, True],
):
strategy.request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
@@ -839,30 +888,37 @@ def test_signin_associates_user_if_oauth_provider_and_tpa_is_required(self):
self.assert_json_success_response_looks_correct(login_user(post_request), verify_redirect_url=True)
def test_first_party_auth_trumps_third_party_auth_but_is_invalid_when_only_email_in_request(self):
- self.assert_first_party_auth_trumps_third_party_auth(email='user@example.com')
+ self.assert_first_party_auth_trumps_third_party_auth(email="user@example.com")
def test_first_party_auth_trumps_third_party_auth_but_is_invalid_when_only_password_in_request(self):
- self.assert_first_party_auth_trumps_third_party_auth(password='password')
+ self.assert_first_party_auth_trumps_third_party_auth(password="password")
def test_first_party_auth_trumps_third_party_auth_and_fails_when_credentials_bad(self):
self.assert_first_party_auth_trumps_third_party_auth(
- email='user@example.com', password='password', success=False)
+ email="user@example.com", password="password", success=False
+ )
def test_first_party_auth_trumps_third_party_auth_and_succeeds_when_credentials_good(self):
self.assert_first_party_auth_trumps_third_party_auth(
- email='user@example.com', password='password', success=True)
+ email="user@example.com", password="password", success=True
+ )
def test_pipeline_redirects_to_requested_url(self):
- requested_redirect_url = 'foo' # something different from '/dashboard'
- request, strategy = self.get_request_and_strategy(redirect_uri='social:complete')
+ requested_redirect_url = "foo" # something different from '/dashboard'
+ request, strategy = self.get_request_and_strategy(redirect_uri="social:complete")
strategy.request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
request.session[pipeline.AUTH_REDIRECT_KEY] = requested_redirect_url
- user = self.create_user_models_for_existing_account(strategy, 'user@foo.com', 'password', self.get_username())
+ user = self.create_user_models_for_existing_account(strategy, "user@foo.com", "password", self.get_username())
self.set_logged_in_cookies(request)
self.assert_redirect_after_pipeline_completes(
- actions.do_complete(request.backend, social_views._do_login, user=user, request=request), # pylint: disable=protected-access
+ actions.do_complete(
+ request.backend,
+ social_views._do_login, # pylint: disable=protected-access
+ user=user,
+ request=request,
+ ),
requested_redirect_url,
)
@@ -870,44 +926,47 @@ def test_full_pipeline_succeeds_registering_new_account(self):
# First, create, the request and strategy that store pipeline state.
# Mock out wire traffic.
request, strategy = self.get_request_and_strategy(
- auth_entry=pipeline.AUTH_ENTRY_REGISTER, redirect_uri='social:complete')
+ auth_entry=pipeline.AUTH_ENTRY_REGISTER, redirect_uri="social:complete"
+ )
strategy.request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
- partial_pipeline_token = strategy.session_get('partial_pipeline_token')
+ partial_pipeline_token = strategy.session_get("partial_pipeline_token")
partial_data = strategy.storage.partial.load(partial_pipeline_token)
# Begin! Grab the registration page and check the login control on it.
- self.assert_register_response_before_pipeline_looks_correct(self.client.get('/register'))
+ self.assert_register_response_before_pipeline_looks_correct(self.client.get("/register"))
# The pipeline starts by a user GETting /auth/login/.
# Synthesize that request and check that it redirects to the correct
# provider page.
- self.assert_redirect_to_provider_looks_correct(self.client.get(
- pipeline.get_login_url(self.provider.provider_id, pipeline.AUTH_ENTRY_LOGIN)))
+ self.assert_redirect_to_provider_looks_correct(
+ self.client.get(pipeline.get_login_url(self.provider.provider_id, pipeline.AUTH_ENTRY_LOGIN))
+ )
# Next, the provider makes a request against /auth/complete/.
# pylint: disable=protected-access
- self.assert_redirect_to_register_looks_correct(actions.do_complete(request.backend, social_views._do_login,
- request=request))
+ self.assert_redirect_to_register_looks_correct(
+ actions.do_complete(request.backend, social_views._do_login, request=request)
+ )
# At this point we know the pipeline has resumed correctly. Next we
# fire off the view that displays the registration form.
with self._patch_edxmako_current_request(request):
self.assert_register_form_populates_unicode_username_correctly(request)
self.assert_register_response_in_pipeline_looks_correct(
- login_and_registration_form(strategy.request, initial_mode='register'),
- pipeline.get(request)['kwargs'],
- ['name', 'username', 'email']
+ login_and_registration_form(strategy.request, initial_mode="register"),
+ pipeline.get(request)["kwargs"],
+ ["name", "username", "email"],
)
# Next, we invoke the view that handles the POST. Not all providers
# supply email. Manually add it as the user would have to; this
# also serves as a test of overriding provider values. Always provide a
# password for us to check that we override it properly.
- overridden_password = strategy.request.POST.get('password')
- email = 'new@example.com'
+ overridden_password = strategy.request.POST.get("password")
+ email = "new@example.com"
- if not strategy.request.POST.get('email'):
- strategy.request.POST = self.get_registration_post_vars({'email': email})
+ if not strategy.request.POST.get("email"):
+ strategy.request.POST = self.get_registration_post_vars({"email": email})
# The user must not exist yet...
with pytest.raises(auth_models.User.DoesNotExist):
@@ -935,41 +994,44 @@ def test_full_pipeline_succeeds_registering_new_account(self):
self.assert_redirect_after_pipeline_completes(
self.do_complete(strategy, request, partial_pipeline_token, partial_data, created_user)
)
- # Now the user has been redirected to the dashboard. Their third party account should now be linked.
+ # Their third party account should now be linked.
self.assert_social_auth_exists_for_user(created_user, strategy)
- self.assert_account_settings_context_looks_correct(account_settings_context(request), linked=True)
+ self.assert_third_party_accounts_state(request, linked=True)
def test_new_account_registration_assigns_distinct_username_on_collision(self):
original_username = self.get_username()
request, strategy = self.get_request_and_strategy(
- auth_entry=pipeline.AUTH_ENTRY_REGISTER, redirect_uri='social:complete')
+ auth_entry=pipeline.AUTH_ENTRY_REGISTER, redirect_uri="social:complete"
+ )
# Create a colliding username in the backend, then proceed with
# assignment via pipeline to make sure a distinct username is created.
- strategy.storage.user.create_user(username=self.get_username(), email='user@email.com', password='password')
+ strategy.storage.user.create_user(username=self.get_username(), email="user@email.com", password="password")
backend = strategy.request.backend
backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
# pylint: disable=protected-access
response = actions.do_complete(backend, social_views._do_login, request=request)
assert response.status_code == 302
- response = json.loads(create_account(strategy.request).content.decode('utf-8'))
- assert response['username'] != original_username
+ response = json.loads(create_account(strategy.request).content.decode("utf-8"))
+ assert response["username"] != original_username
def test_new_account_registration_fails_if_email_exists(self):
request, strategy = self.get_request_and_strategy(
- auth_entry=pipeline.AUTH_ENTRY_REGISTER, redirect_uri='social:complete')
+ auth_entry=pipeline.AUTH_ENTRY_REGISTER, redirect_uri="social:complete"
+ )
backend = strategy.request.backend
backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
# pylint: disable=protected-access
- self.assert_redirect_to_register_looks_correct(actions.do_complete(backend, social_views._do_login,
- request=request))
+ self.assert_redirect_to_register_looks_correct(
+ actions.do_complete(backend, social_views._do_login, request=request)
+ )
with self._patch_edxmako_current_request(request):
self.assert_register_response_in_pipeline_looks_correct(
- login_and_registration_form(strategy.request, initial_mode='register'),
- pipeline.get(request)['kwargs'],
- ['name', 'username', 'email']
+ login_and_registration_form(strategy.request, initial_mode="register"),
+ pipeline.get(request)["kwargs"],
+ ["name", "username", "email"],
)
with self._patch_edxmako_current_request(strategy.request):
@@ -979,18 +1041,18 @@ def test_new_account_registration_fails_if_email_exists(self):
self.assert_json_failure_response_is_username_collision(create_account(strategy.request))
def test_pipeline_raises_auth_entry_error_if_auth_entry_invalid(self):
- auth_entry = 'invalid'
+ auth_entry = "invalid"
assert auth_entry not in pipeline._AUTH_ENTRY_CHOICES # pylint: disable=protected-access
- _, strategy = self.get_request_and_strategy(auth_entry=auth_entry, redirect_uri='social:complete')
+ _, strategy = self.get_request_and_strategy(auth_entry=auth_entry, redirect_uri="social:complete")
with pytest.raises(pipeline.AuthEntryError):
strategy.request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
def test_pipeline_assumes_login_if_auth_entry_missing(self):
- _, strategy = self.get_request_and_strategy(auth_entry=None, redirect_uri='social:complete')
+ _, strategy = self.get_request_and_strategy(auth_entry=None, redirect_uri="social:complete")
response = self.fake_auth_complete(strategy)
- assert response.url == reverse('signin_user')
+ assert response.url == reverse("signin_user")
def assert_first_party_auth_trumps_third_party_auth(self, email=None, password=None, success=None):
"""Asserts first party auth was used in place of third party auth.
@@ -1004,33 +1066,35 @@ def assert_first_party_auth_trumps_third_party_auth(self, email=None, password=N
one of username or password will be missing).
"""
_, strategy = self.get_request_and_strategy(
- auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
+ auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri="social:complete"
+ )
strategy.request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
self.create_user_models_for_existing_account(
- strategy, email, password, self.get_username(), skip_social_auth=True)
+ strategy, email, password, self.get_username(), skip_social_auth=True
+ )
post_request = self._get_login_post_request(strategy)
post_request.POST = dict(post_request.POST)
if email:
- post_request.POST['email'] = email
+ post_request.POST["email"] = email
if password:
- post_request.POST['password'] = 'bad_' + password if success is False else password
+ post_request.POST["password"] = "bad_" + password if success is False else password
self.assert_pipeline_running(post_request)
- payload = json.loads(login_user(post_request).content.decode('utf-8'))
+ payload = json.loads(login_user(post_request).content.decode("utf-8"))
if success is None:
# Request malformed -- just one of email/password given.
- assert not payload.get('success')
- assert 'There was an error receiving your login information' in payload.get('value')
+ assert not payload.get("success")
+ assert "There was an error receiving your login information" in payload.get("value")
elif success:
# Request well-formed and credentials good.
- assert payload.get('success')
+ assert payload.get("success")
else:
# Request well-formed but credentials bad.
- assert not payload.get('success')
- assert 'incorrect' in payload.get('value')
+ assert not payload.get("success")
+ assert "incorrect" in payload.get("value")
def get_response_data(self):
"""Gets a dict of response data of the form given by the provider.
@@ -1064,8 +1128,13 @@ def do_complete(self, strategy, request, partial_pipeline_token, partial_data, u
if not user:
user = request.user
return actions.do_complete(
- request.backend, social_views._do_login, user, None, # pylint: disable=protected-access
- redirect_field_name=auth.REDIRECT_FIELD_NAME, request=request, partial_token=partial_pipeline_token
+ request.backend,
+ social_views._do_login, # pylint: disable=protected-access
+ user,
+ None,
+ redirect_field_name=auth.REDIRECT_FIELD_NAME,
+ request=request,
+ partial_token=partial_pipeline_token,
)
diff --git a/common/djangoapps/third_party_auth/tests/specs/test_testshib.py b/common/djangoapps/third_party_auth/tests/specs/test_testshib.py
index ec3efd8286e7..caddd325ba76 100644
--- a/common/djangoapps/third_party_auth/tests/specs/test_testshib.py
+++ b/common/djangoapps/third_party_auth/tests/specs/test_testshib.py
@@ -2,7 +2,6 @@
Third_party_auth integration tests using a mock version of the TestShib provider
"""
-
import datetime
import json
import logging
@@ -27,16 +26,15 @@
from common.djangoapps.third_party_auth.saml import log as saml_log
from common.djangoapps.third_party_auth.tasks import fetch_saml_metadata
from common.djangoapps.third_party_auth.tests import testutil, utils
-from openedx.core.djangoapps.user_api.accounts.settings_views import account_settings_context
from openedx.core.djangoapps.user_authn.views.login import login_user
from openedx.features.enterprise_support.tests.factories import EnterpriseCustomerFactory
from .base import IntegrationTestMixin
-TESTSHIB_ENTITY_ID = 'https://idp.testshib.org/idp/shibboleth'
-TESTSHIB_METADATA_URL = 'https://mock.testshib.org/metadata/testshib-providers.xml'
-TESTSHIB_METADATA_URL_WITH_CACHE_DURATION = 'https://mock.testshib.org/metadata/testshib-providers-cache.xml'
-TESTSHIB_SSO_URL = 'https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO'
+TESTSHIB_ENTITY_ID = "https://idp.testshib.org/idp/shibboleth"
+TESTSHIB_METADATA_URL = "https://mock.testshib.org/metadata/testshib-providers.xml"
+TESTSHIB_METADATA_URL_WITH_CACHE_DURATION = "https://mock.testshib.org/metadata/testshib-providers-cache.xml"
+TESTSHIB_SSO_URL = "https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO"
class SamlIntegrationTestUtilities:
@@ -44,6 +42,7 @@ class SamlIntegrationTestUtilities:
Class contains methods particular to SAML integration testing so that they
can be separated out from the actual test methods.
"""
+
PROVIDER_ID = "saml-testshib"
PROVIDER_NAME = "TestShib"
PROVIDER_BACKEND = "tpa-saml"
@@ -67,51 +66,59 @@ def setUp(self):
self.addCleanup(httpretty.disable) # lint-amnesty, pylint: disable=no-member
def metadata_callback(_request, _uri, headers):
- """ Return a cached copy of TestShib's metadata by reading it from disk """
- return (200, headers, self.read_data_file('testshib_metadata.xml')) # lint-amnesty, pylint: disable=no-member
+ """Return a cached copy of TestShib's metadata by reading it from disk"""
+ return (
+ 200,
+ headers,
+ self.read_data_file("testshib_metadata.xml"),
+ ) # lint-amnesty, pylint: disable=no-member
- httpretty.register_uri(httpretty.GET, TESTSHIB_METADATA_URL, content_type='text/xml', body=metadata_callback)
+ httpretty.register_uri(httpretty.GET, TESTSHIB_METADATA_URL, content_type="text/xml", body=metadata_callback)
def cache_duration_metadata_callback(_request, _uri, headers):
"""Return a cached copy of TestShib's metadata with a cacheDuration attribute"""
- return (200, headers, self.read_data_file('testshib_metadata_with_cache_duration.xml')) # lint-amnesty, pylint: disable=no-member
+ return (
+ 200,
+ headers,
+ self.read_data_file("testshib_metadata_with_cache_duration.xml"),
+ ) # lint-amnesty, pylint: disable=no-member
httpretty.register_uri(
httpretty.GET,
TESTSHIB_METADATA_URL_WITH_CACHE_DURATION,
- content_type='text/xml',
- body=cache_duration_metadata_callback
+ content_type="text/xml",
+ body=cache_duration_metadata_callback,
)
# Configure the SAML library to use the same request ID for every request.
# Doing this and freezing the time allows us to play back recorded request/response pairs
- uid_patch = patch('onelogin.saml2.utils.OneLogin_Saml2_Utils.generate_unique_id', return_value='TESTID')
+ uid_patch = patch("onelogin.saml2.utils.OneLogin_Saml2_Utils.generate_unique_id", return_value="TESTID")
uid_patch.start()
self.addCleanup(uid_patch.stop) # lint-amnesty, pylint: disable=no-member
self._freeze_time(timestamp=1434326820) # This is the time when the saved request/response was recorded.
def _freeze_time(self, timestamp):
- """ Mock the current time for SAML, so we can replay canned requests/responses """
- now_patch = patch('onelogin.saml2.utils.OneLogin_Saml2_Utils.now', return_value=timestamp)
+ """Mock the current time for SAML, so we can replay canned requests/responses"""
+ now_patch = patch("onelogin.saml2.utils.OneLogin_Saml2_Utils.now", return_value=timestamp)
now_patch.start()
self.addCleanup(now_patch.stop) # lint-amnesty, pylint: disable=no-member
def _configure_testshib_provider(self, **kwargs):
- """ Enable and configure the TestShib SAML IdP as a third_party_auth provider """
- fetch_metadata = kwargs.pop('fetch_metadata', True)
- assert_metadata_updates = kwargs.pop('assert_metadata_updates', True)
- kwargs.setdefault('name', self.PROVIDER_NAME)
- kwargs.setdefault('enabled', True)
- kwargs.setdefault('visible', True)
+ """Enable and configure the TestShib SAML IdP as a third_party_auth provider"""
+ fetch_metadata = kwargs.pop("fetch_metadata", True)
+ assert_metadata_updates = kwargs.pop("assert_metadata_updates", True)
+ kwargs.setdefault("name", self.PROVIDER_NAME)
+ kwargs.setdefault("enabled", True)
+ kwargs.setdefault("visible", True)
kwargs.setdefault("backend_name", "tpa-saml")
- kwargs.setdefault('slug', self.PROVIDER_IDP_SLUG)
- kwargs.setdefault('entity_id', TESTSHIB_ENTITY_ID)
- kwargs.setdefault('metadata_source', TESTSHIB_METADATA_URL)
- kwargs.setdefault('icon_class', 'fa-university')
- kwargs.setdefault('attr_email', 'urn:oid:1.3.6.1.4.1.5923.1.1.1.6') # eduPersonPrincipalName
- kwargs.setdefault('max_session_length', None)
- kwargs.setdefault('send_to_registration_first', False)
- kwargs.setdefault('skip_email_verification', False)
+ kwargs.setdefault("slug", self.PROVIDER_IDP_SLUG)
+ kwargs.setdefault("entity_id", TESTSHIB_ENTITY_ID)
+ kwargs.setdefault("metadata_source", TESTSHIB_METADATA_URL)
+ kwargs.setdefault("icon_class", "fa-university")
+ kwargs.setdefault("attr_email", "urn:oid:1.3.6.1.4.1.5923.1.1.1.6") # eduPersonPrincipalName
+ kwargs.setdefault("max_session_length", None)
+ kwargs.setdefault("send_to_registration_first", False)
+ kwargs.setdefault("skip_email_verification", False)
saml_provider = self.configure_saml_provider(**kwargs) # pylint: disable=no-member
if fetch_metadata:
@@ -127,17 +134,17 @@ def _configure_testshib_provider(self, **kwargs):
return saml_provider
def do_provider_login(self, provider_redirect_url):
- """ Mocked: the user logs in to TestShib and then gets redirected back """
+ """Mocked: the user logs in to TestShib and then gets redirected back"""
# The SAML provider (TestShib) will authenticate the user, then get the browser to POST a response:
assert provider_redirect_url.startswith(TESTSHIB_SSO_URL) # lint-amnesty, pylint: disable=no-member
saml_response_xml = utils.read_and_pre_process_xml(
- os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data', 'testshib_saml_response.xml')
+ os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "testshib_saml_response.xml")
)
return self.client.post( # lint-amnesty, pylint: disable=no-member
self.complete_url, # lint-amnesty, pylint: disable=no-member
- content_type='application/x-www-form-urlencoded',
+ content_type="application/x-www-form-urlencoded",
data=utils.prepare_saml_response_from_xml(saml_response_xml),
)
@@ -150,16 +157,16 @@ class TestIndexExceptionTest(SamlIntegrationTestUtilities, IntegrationTestMixin,
"""
TOKEN_RESPONSE_DATA = {
- 'access_token': 'access_token_value',
- 'expires_in': 'expires_in_value',
+ "access_token": "access_token_value",
+ "expires_in": "expires_in_value",
}
USER_RESPONSE_DATA = {
- 'lastName': 'lastName_value',
- 'id': 'id_value',
- 'firstName': 'firstName_value',
- 'idp_name': 'testshib',
- 'attributes': {'urn:oid:0.9.2342.19200300.100.1.1': [], 'name_id': '1'},
- 'session_index': '1',
+ "lastName": "lastName_value",
+ "id": "id_value",
+ "firstName": "firstName_value",
+ "idp_name": "testshib",
+ "attributes": {"urn:oid:0.9.2342.19200300.100.1.1": [], "name_id": "1"},
+ "session_index": "1",
}
def test_index_error_from_empty_list_saml_attribute(self):
@@ -169,7 +176,8 @@ def test_index_error_from_empty_list_saml_attribute(self):
"""
self.provider = self._configure_testshib_provider()
request, strategy = self.get_request_and_strategy(
- auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
+ auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri="social:complete"
+ )
with self.assertRaises(IncorrectConfigurationException):
request.backend.auth_complete = MagicMock(return_value=self.fake_auth_complete(strategy))
@@ -188,16 +196,16 @@ class TestKeyExceptionTest(SamlIntegrationTestUtilities, IntegrationTestMixin, t
"""
TOKEN_RESPONSE_DATA = {
- 'access_token': 'access_token_value',
- 'expires_in': 'expires_in_value',
+ "access_token": "access_token_value",
+ "expires_in": "expires_in_value",
}
USER_RESPONSE_DATA = {
- 'lastName': 'lastName_value',
- 'id': 'id_value',
- 'firstName': 'firstName_value',
- 'idp_name': 'testshib',
- 'attributes': {'name_id': '1'},
- 'session_index': '1',
+ "lastName": "lastName_value",
+ "id": "id_value",
+ "firstName": "firstName_value",
+ "idp_name": "testshib",
+ "attributes": {"name_id": "1"},
+ "session_index": "1",
}
def test_key_error_from_missing_saml_attributes(self):
@@ -207,7 +215,8 @@ def test_key_error_from_missing_saml_attributes(self):
"""
self.provider = self._configure_testshib_provider()
request, strategy = self.get_request_and_strategy(
- auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
+ auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri="social:complete"
+ )
with self.assertRaises(IncorrectConfigurationException):
request.backend.auth_complete = MagicMock(return_value=self.fake_auth_complete(strategy))
@@ -226,25 +235,23 @@ class TestShibIntegrationTest(SamlIntegrationTestUtilities, IntegrationTestMixin
"""
TOKEN_RESPONSE_DATA = {
- 'access_token': 'access_token_value',
- 'expires_in': 'expires_in_value',
+ "access_token": "access_token_value",
+ "expires_in": "expires_in_value",
}
USER_RESPONSE_DATA = {
- 'lastName': 'lastName_value',
- 'id': 'id_value',
- 'firstName': 'firstName_value',
- 'idp_name': 'testshib',
- 'attributes': {'urn:oid:0.9.2342.19200300.100.1.1': ['myself'], 'name_id': '1'},
- 'session_index': '1',
+ "lastName": "lastName_value",
+ "id": "id_value",
+ "firstName": "firstName_value",
+ "idp_name": "testshib",
+ "attributes": {"urn:oid:0.9.2342.19200300.100.1.1": ["myself"], "name_id": "1"},
+ "session_index": "1",
}
- @patch('openedx.features.enterprise_support.api.enterprise_customer_for_request')
- @patch('openedx.core.djangoapps.user_api.accounts.settings_views.enterprise_customer_for_request')
- @patch('openedx.features.enterprise_support.utils.third_party_auth.provider.Registry.get')
+ @patch("openedx.features.enterprise_support.api.enterprise_customer_for_request")
+ @patch("openedx.features.enterprise_support.utils.third_party_auth.provider.Registry.get")
def test_full_pipeline_succeeds_for_unlinking_testshib_account(
self,
mock_auth_provider,
- mock_enterprise_customer_for_request_settings_view,
mock_enterprise_customer_for_request,
):
@@ -252,10 +259,12 @@ def test_full_pipeline_succeeds_for_unlinking_testshib_account(
# configure the backend, and mock out wire traffic.
self.provider = self._configure_testshib_provider()
request, strategy = self.get_request_and_strategy(
- auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
+ auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri="social:complete"
+ )
request.backend.auth_complete = MagicMock(return_value=self.fake_auth_complete(strategy))
user = self.create_user_models_for_existing_account(
- strategy, 'user@example.com', 'password', self.get_username())
+ strategy, "user@example.com", "password", self.get_username()
+ )
self.assert_social_auth_exists_for_user(user, strategy)
request.user = user
@@ -267,70 +276,67 @@ def test_full_pipeline_succeeds_for_unlinking_testshib_account(
enterprise_customer = EnterpriseCustomerFactory()
assert EnterpriseCustomerUser.objects.count() == 0, "Precondition check: no link records should exist"
EnterpriseCustomerUser.objects.link_user(enterprise_customer, user.email)
- assert (EnterpriseCustomerUser.objects
- .filter(enterprise_customer=enterprise_customer, user_id=user.id).count() == 1)
- EnterpriseCustomerIdentityProvider.objects.get_or_create(enterprise_customer=enterprise_customer,
- provider_id=self.provider.provider_id)
+ assert (
+ EnterpriseCustomerUser.objects.filter(enterprise_customer=enterprise_customer, user_id=user.id).count() == 1
+ )
+ EnterpriseCustomerIdentityProvider.objects.get_or_create(
+ enterprise_customer=enterprise_customer, provider_id=self.provider.provider_id
+ )
enterprise_customer_data = {
- 'uuid': enterprise_customer.uuid,
- 'name': enterprise_customer.name,
- 'identity_provider': 'saml-default',
- 'identity_providers': [
+ "uuid": enterprise_customer.uuid,
+ "name": enterprise_customer.name,
+ "identity_provider": "saml-default",
+ "identity_providers": [
{
"provider_id": "saml-default",
}
],
}
- mock_auth_provider.return_value.backend_name = 'tpa-saml'
+ mock_auth_provider.return_value.backend_name = "tpa-saml"
mock_enterprise_customer_for_request.return_value = enterprise_customer_data
- mock_enterprise_customer_for_request_settings_view.return_value = enterprise_customer_data
# Instrument the pipeline to get to the dashboard with the full expected state.
- self.client.get(
- pipeline.get_login_url(self.provider.provider_id, pipeline.AUTH_ENTRY_LOGIN))
+ self.client.get(pipeline.get_login_url(self.provider.provider_id, pipeline.AUTH_ENTRY_LOGIN))
- actions.do_complete(request.backend, social_views._do_login, # pylint: disable=protected-access
- request=request)
+ actions.do_complete(
+ request.backend, social_views._do_login, request=request # pylint: disable=protected-access
+ )
with self._patch_edxmako_current_request(strategy.request):
login_user(strategy.request)
- actions.do_complete(request.backend, social_views._do_login, user=user, # pylint: disable=protected-access
- request=request)
+ actions.do_complete(
+ request.backend, social_views._do_login, user=user, request=request # pylint: disable=protected-access
+ )
# First we expect that we're in the linked state, with a backend entry.
- self.assert_account_settings_context_looks_correct(account_settings_context(request), linked=True)
self.assert_social_auth_exists_for_user(request.user, strategy)
FEATURES_WITH_ENTERPRISE_ENABLED = settings.FEATURES.copy()
- FEATURES_WITH_ENTERPRISE_ENABLED['ENABLE_ENTERPRISE_INTEGRATION'] = True
+ FEATURES_WITH_ENTERPRISE_ENABLED["ENABLE_ENTERPRISE_INTEGRATION"] = True
with patch.dict("django.conf.settings.FEATURES", FEATURES_WITH_ENTERPRISE_ENABLED):
# Fire off the disconnect pipeline without the user information.
actions.do_disconnect(
- request.backend,
- None,
- None,
- redirect_field_name=auth.REDIRECT_FIELD_NAME,
- request=request
+ request.backend, None, None, redirect_field_name=auth.REDIRECT_FIELD_NAME, request=request
+ )
+ assert (
+ EnterpriseCustomerUser.objects.filter(enterprise_customer=enterprise_customer, user_id=user.id).count()
+ != 0
)
- assert EnterpriseCustomerUser.objects\
- .filter(enterprise_customer=enterprise_customer, user_id=user.id).count() != 0
# Fire off the disconnect pipeline to unlink.
self.assert_redirect_after_pipeline_completes(
actions.do_disconnect(
- request.backend,
- user,
- None,
- redirect_field_name=auth.REDIRECT_FIELD_NAME,
- request=request
+ request.backend, user, None, redirect_field_name=auth.REDIRECT_FIELD_NAME, request=request
)
)
# Now we expect to be in the unlinked state, with no backend entry.
- self.assert_account_settings_context_looks_correct(account_settings_context(request), linked=False)
+ self.assert_third_party_accounts_state(request, linked=False)
self.assert_social_auth_does_not_exist_for_user(user, strategy)
- assert EnterpriseCustomerUser.objects\
- .filter(enterprise_customer=enterprise_customer, user_id=user.id).count() == 0
+ assert (
+ EnterpriseCustomerUser.objects.filter(enterprise_customer=enterprise_customer, user_id=user.id).count()
+ == 0
+ )
def get_response_data(self):
"""Gets dict (string -> object) of merged data about the user."""
@@ -340,7 +346,7 @@ def get_response_data(self):
def get_username(self):
response_data = self.get_response_data()
- return response_data.get('idp_name')
+ return response_data.get("idp_name")
def test_login_before_metadata_fetched(self):
self._configure_testshib_provider(fetch_metadata=False)
@@ -350,18 +356,18 @@ def test_login_before_metadata_fetched(self):
try_login_response = self.client.get(testshib_login_url)
# The user should be redirected to back to the login page:
assert try_login_response.status_code == 302
- assert try_login_response['Location'] == self.login_page_url
+ assert try_login_response["Location"] == self.login_page_url
# When loading the login page, the user will see an error message:
response = self.client.get(self.login_page_url)
- self.assertContains(response, 'Authentication with TestShib is currently unavailable.')
+ self.assertContains(response, "Authentication with TestShib is currently unavailable.")
def test_login(self):
- """ Configure TestShib before running the login test """
+ """Configure TestShib before running the login test"""
self._configure_testshib_provider()
self._test_login()
def test_register(self):
- """ Configure TestShib before running the register test """
+ """Configure TestShib before running the register test"""
self._configure_testshib_provider()
self._test_register()
@@ -374,17 +380,17 @@ def test_login_records_attributes(self):
user=self.user, provider=self.PROVIDER_BACKEND, uid__startswith=self.PROVIDER_IDP_SLUG
)
attributes = record.extra_data
- assert attributes.get('urn:oid:1.3.6.1.4.1.5923.1.1.1.9') == ['Member@testshib.org', 'Staff@testshib.org']
- assert attributes.get('urn:oid:2.5.4.3') == ['Me Myself And I']
- assert attributes.get('urn:oid:0.9.2342.19200300.100.1.1') == ['myself']
- assert attributes.get('urn:oid:2.5.4.20') == ['555-5555']
+ assert attributes.get("urn:oid:1.3.6.1.4.1.5923.1.1.1.9") == ["Member@testshib.org", "Staff@testshib.org"]
+ assert attributes.get("urn:oid:2.5.4.3") == ["Me Myself And I"]
+ assert attributes.get("urn:oid:0.9.2342.19200300.100.1.1") == ["myself"]
+ assert attributes.get("urn:oid:2.5.4.20") == ["555-5555"]
# Phone number
@ddt.data(True, False)
def test_debug_mode_login(self, debug_mode_enabled):
- """ Test SAML login logs with debug mode enabled or not """
+ """Test SAML login logs with debug mode enabled or not"""
self._configure_testshib_provider(debug_mode=debug_mode_enabled)
- with patch.object(saml_log, 'info') as mock_log:
+ with patch.object(saml_log, "info") as mock_log:
self._test_login()
if debug_mode_enabled:
# We expect that test_login() does two full logins, and each attempt generates two
@@ -393,38 +399,37 @@ def test_debug_mode_login(self, debug_mode_enabled):
expected_next_url = "/dashboard"
(msg, action_type, idp_name, request_data, next_url, xml), _kwargs = mock_log.call_args_list[0]
- assert msg.startswith('SAML login %s')
- assert action_type == 'request'
+ assert msg.startswith("SAML login %s")
+ assert action_type == "request"
assert idp_name == self.PROVIDER_IDP_SLUG
self.assertDictContainsSubset(
- {"idp": idp_name, "auth_entry": "login", "next": expected_next_url},
- request_data
+ {"idp": idp_name, "auth_entry": "login", "next": expected_next_url}, request_data
)
assert next_url == expected_next_url
- assert '');
- });
-
- it('shows loading error when UserAccountModel fails to load', function() {
- requests = AjaxHelpers.requests(this);
-
- var accountSettingsView = createAccountSettingsPage();
-
- Helpers.expectLoadingErrorIsVisible(accountSettingsView, false);
-
- var request = requests[0];
- expect(request.method).toBe('GET');
- expect(request.url).toBe(Helpers.USER_ACCOUNTS_API_URL);
-
- AjaxHelpers.respondWithError(requests, 500);
- Helpers.expectLoadingErrorIsVisible(accountSettingsView, true);
- });
-
- it('shows loading error when UserPreferencesModel fails to load', function() {
- requests = AjaxHelpers.requests(this);
-
- var accountSettingsView = createAccountSettingsPage();
-
- Helpers.expectLoadingErrorIsVisible(accountSettingsView, false);
-
- var request = requests[0];
- expect(request.method).toBe('GET');
- expect(request.url).toBe(Helpers.USER_ACCOUNTS_API_URL);
-
- AjaxHelpers.respondWithJson(requests, Helpers.createAccountSettingsData());
- Helpers.expectLoadingErrorIsVisible(accountSettingsView, false);
-
- request = requests[1];
- expect(request.method).toBe('GET');
- expect(request.url).toBe('/api/user/v1/preferences/time_zones/?country_code=1');
- AjaxHelpers.respondWithJson(requests, Helpers.TIME_ZONE_RESPONSE);
-
- request = requests[2];
- expect(request.method).toBe('GET');
- expect(request.url).toBe(Helpers.USER_PREFERENCES_API_URL);
-
- AjaxHelpers.respondWithError(requests, 500);
- Helpers.expectLoadingErrorIsVisible(accountSettingsView, true);
- });
-
- it('renders fields after the models are successfully fetched', function() {
- requests = AjaxHelpers.requests(this);
-
- var accountSettingsView = createAccountSettingsPage();
-
- Helpers.expectLoadingErrorIsVisible(accountSettingsView, false);
-
- AjaxHelpers.respondWithJson(requests, Helpers.createAccountSettingsData());
- AjaxHelpers.respondWithJson(requests, Helpers.TIME_ZONE_RESPONSE);
- AjaxHelpers.respondWithJson(requests, Helpers.createUserPreferencesData());
-
- accountSettingsView.render();
-
- Helpers.expectLoadingErrorIsVisible(accountSettingsView, false);
- Helpers.expectSettingsSectionsAndFieldsToBeRendered(accountSettingsView);
- });
-
- it('expects all fields to behave correctly', function() {
- var i, view;
-
- requests = AjaxHelpers.requests(this);
-
- var accountSettingsView = createAccountSettingsPage();
-
- AjaxHelpers.respondWithJson(requests, Helpers.createAccountSettingsData());
- AjaxHelpers.respondWithJson(requests, Helpers.TIME_ZONE_RESPONSE);
- AjaxHelpers.respondWithJson(requests, Helpers.createUserPreferencesData());
- AjaxHelpers.respondWithJson(requests, {}); // Page viewed analytics event
-
- var sectionsData = accountSettingsView.options.tabSections.aboutTabSections;
-
- expect(sectionsData[0].fields.length).toBe(7);
-
- var textFields = [sectionsData[0].fields[1], sectionsData[0].fields[2]];
- for (i = 0; i < textFields.length; i++) {
- view = textFields[i].view;
- FieldViewsSpecHelpers.verifyTextField(view, {
- title: view.options.title,
- valueAttribute: view.options.valueAttribute,
- helpMessage: view.options.helpMessage,
- validValue: 'My Name',
- invalidValue1: '',
- invalidValue2: '@',
- validationError: 'Think again!',
- defaultValue: ''
- }, requests);
- }
-
- expect(sectionsData[1].fields.length).toBe(4);
- var dropdownFields = [
- sectionsData[1].fields[0],
- sectionsData[1].fields[1],
- sectionsData[1].fields[2]
- ];
- _.each(dropdownFields, function(field) {
- // eslint-disable-next-line no-shadow
- var view = field.view;
- FieldViewsSpecHelpers.verifyDropDownField(view, {
- title: view.options.title,
- valueAttribute: view.options.valueAttribute,
- helpMessage: '',
- validValue: Helpers.FIELD_OPTIONS[1][0],
- invalidValue1: Helpers.FIELD_OPTIONS[2][0],
- invalidValue2: Helpers.FIELD_OPTIONS[3][0],
- validationError: 'Nope, this will not do!',
- defaultValue: null
- }, requests);
- });
- });
- });
-
- describe('edx.user.AccountSettingsFactory', function() {
- var createEnterpriseLearnerAccountSettingsPage = function() {
- var context = AccountSettingsPage(
- Helpers.FIELDS_DATA,
- false,
- [],
- Helpers.AUTH_DATA,
- Helpers.PASSWORD_RESET_SUPPORT_LINK,
- Helpers.USER_ACCOUNTS_API_URL,
- Helpers.USER_PREFERENCES_API_URL,
- 1,
- Helpers.PLATFORM_NAME,
- Helpers.CONTACT_EMAIL,
- true,
- Helpers.ENABLE_COPPA_COMPLIANCE,
- '',
-
- Helpers.SYNC_LEARNER_PROFILE_DATA,
- Helpers.ENTERPRISE_NAME,
- Helpers.ENTERPRISE_READ_ONLY_ACCOUNT_FIELDS,
- Helpers.EDX_SUPPORT_URL
- );
- return context.accountSettingsView;
- };
-
- var requests;
- var accountInfoTab = {
- BASIC_ACCOUNT_INFORMATION: 0,
- ADDITIONAL_INFORMATION: 1
- };
- var basicAccountInfoFields = {
- USERNAME: 0,
- FULL_NAME: 1,
- EMAIL_ADDRESS: 2,
- PASSWORD: 3,
- LANGUAGE: 4,
- COUNTRY: 5,
- TIMEZONE: 6
- };
- var additionalInfoFields = {
- EDUCATION: 0,
- GENDER: 1,
- YEAR_OF_BIRTH: 2,
- PREFERRED_LANGUAGE: 3
- };
-
- beforeEach(function() {
- setFixtures('');
- });
-
- it('shows loading error when UserAccountModel fails to load for enterprise learners', function() {
- var accountSettingsView, request;
- requests = AjaxHelpers.requests(this);
-
- accountSettingsView = createEnterpriseLearnerAccountSettingsPage();
-
- Helpers.expectLoadingErrorIsVisible(accountSettingsView, false);
-
- request = requests[0];
- expect(request.method).toBe('GET');
- expect(request.url).toBe(Helpers.USER_ACCOUNTS_API_URL);
-
- AjaxHelpers.respondWithError(requests, 500);
- Helpers.expectLoadingErrorIsVisible(accountSettingsView, true);
- });
-
- it('shows loading error when UserPreferencesModel fails to load for enterprise learners', function() {
- var accountSettingsView, request;
- requests = AjaxHelpers.requests(this);
-
- accountSettingsView = createEnterpriseLearnerAccountSettingsPage();
-
- Helpers.expectLoadingErrorIsVisible(accountSettingsView, false);
-
- request = requests[0];
- expect(request.method).toBe('GET');
- expect(request.url).toBe(Helpers.USER_ACCOUNTS_API_URL);
-
- AjaxHelpers.respondWithJson(requests, Helpers.createAccountSettingsData());
- Helpers.expectLoadingErrorIsVisible(accountSettingsView, false);
-
- request = requests[1];
- expect(request.method).toBe('GET');
- expect(request.url).toBe('/api/user/v1/preferences/time_zones/?country_code=1');
- AjaxHelpers.respondWithJson(requests, Helpers.TIME_ZONE_RESPONSE);
-
- request = requests[2];
- expect(request.method).toBe('GET');
- expect(request.url).toBe(Helpers.USER_PREFERENCES_API_URL);
-
- AjaxHelpers.respondWithError(requests, 500);
- Helpers.expectLoadingErrorIsVisible(accountSettingsView, true);
- });
-
- it('renders fields after the models are successfully fetched for enterprise learners', function() {
- var accountSettingsView;
- requests = AjaxHelpers.requests(this);
-
- accountSettingsView = createEnterpriseLearnerAccountSettingsPage();
-
- Helpers.expectLoadingErrorIsVisible(accountSettingsView, false);
-
- AjaxHelpers.respondWithJson(requests, Helpers.createAccountSettingsData());
- AjaxHelpers.respondWithJson(requests, Helpers.TIME_ZONE_RESPONSE);
- AjaxHelpers.respondWithJson(requests, Helpers.createUserPreferencesData());
-
- accountSettingsView.render();
-
- Helpers.expectLoadingErrorIsVisible(accountSettingsView, false);
- Helpers.expectSettingsSectionsAndFieldsToBeRenderedWithMessage(accountSettingsView);
- });
-
- it('expects all fields to behave correctly for enterprise learners', function() {
- var accountSettingsView, i, view, sectionsData, textFields, dropdownFields;
- requests = AjaxHelpers.requests(this);
-
- accountSettingsView = createEnterpriseLearnerAccountSettingsPage();
-
- AjaxHelpers.respondWithJson(requests, Helpers.createAccountSettingsData());
- AjaxHelpers.respondWithJson(requests, Helpers.TIME_ZONE_RESPONSE);
- AjaxHelpers.respondWithJson(requests, Helpers.createUserPreferencesData());
- AjaxHelpers.respondWithJson(requests, {}); // Page viewed analytics event
-
- sectionsData = accountSettingsView.options.tabSections.aboutTabSections;
-
- expect(sectionsData[accountInfoTab.BASIC_ACCOUNT_INFORMATION].fields.length).toBe(7);
-
- // Verify that username, name and email fields are readonly
- textFields = [
- sectionsData[accountInfoTab.BASIC_ACCOUNT_INFORMATION].fields[basicAccountInfoFields.USERNAME],
- sectionsData[accountInfoTab.BASIC_ACCOUNT_INFORMATION].fields[basicAccountInfoFields.FULL_NAME],
- sectionsData[accountInfoTab.BASIC_ACCOUNT_INFORMATION].fields[basicAccountInfoFields.EMAIL_ADDRESS]
- ];
- for (i = 0; i < textFields.length; i++) {
- view = textFields[i].view;
-
- FieldViewsSpecHelpers.verifyReadonlyTextField(view, {
- title: view.options.title,
- valueAttribute: view.options.valueAttribute,
- helpMessage: view.options.helpMessage,
- validValue: 'My Name',
- defaultValue: ''
- }, requests);
- }
-
- // Verify un-editable country dropdown field
- view = sectionsData[
- accountInfoTab.BASIC_ACCOUNT_INFORMATION
- ].fields[basicAccountInfoFields.COUNTRY].view;
-
- FieldViewsSpecHelpers.verifyReadonlyDropDownField(view, {
- title: view.options.title,
- valueAttribute: view.options.valueAttribute,
- helpMessage: '',
- validValue: Helpers.FIELD_OPTIONS[1][0],
- editable: 'never',
- defaultValue: null
- });
-
- expect(sectionsData[accountInfoTab.ADDITIONAL_INFORMATION].fields.length).toBe(4);
- dropdownFields = [
- sectionsData[accountInfoTab.ADDITIONAL_INFORMATION].fields[additionalInfoFields.EDUCATION],
- sectionsData[accountInfoTab.ADDITIONAL_INFORMATION].fields[additionalInfoFields.GENDER],
- sectionsData[accountInfoTab.ADDITIONAL_INFORMATION].fields[additionalInfoFields.YEAR_OF_BIRTH]
- ];
- _.each(dropdownFields, function(field) {
- view = field.view;
- FieldViewsSpecHelpers.verifyDropDownField(view, {
- title: view.options.title,
- valueAttribute: view.options.valueAttribute,
- helpMessage: '',
- validValue: Helpers.FIELD_OPTIONS[1][0], // dummy option for dropdown field
- invalidValue1: Helpers.FIELD_OPTIONS[2][0], // dummy option for dropdown field
- invalidValue2: Helpers.FIELD_OPTIONS[3][0], // dummy option for dropdown field
- validationError: 'Nope, this will not do!',
- defaultValue: null
- }, requests);
- });
- });
- });
-});
diff --git a/lms/static/js/spec/student_account/account_settings_fields_helpers.js b/lms/static/js/spec/student_account/account_settings_fields_helpers.js
deleted file mode 100644
index 4aea86b235a3..000000000000
--- a/lms/static/js/spec/student_account/account_settings_fields_helpers.js
+++ /dev/null
@@ -1,34 +0,0 @@
-define(['backbone',
- 'jquery',
- 'underscore',
- 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
- 'common/js/spec_helpers/template_helpers',
- 'js/spec/views/fields_helpers',
- 'string_utils'],
-function(Backbone, $, _, AjaxHelpers, TemplateHelpers, FieldViewsSpecHelpers) {
- 'use strict';
-
- var verifyAuthField = function(view, data, requests) {
- var selector = '.u-field-value .u-field-link-title-' + view.options.valueAttribute;
-
- spyOn(view, 'redirect_to');
-
- FieldViewsSpecHelpers.expectTitleAndMessageToContain(view, data.title, data.helpMessage);
- expect(view.$(selector).text().trim()).toBe('Unlink This Account');
- view.$(selector).click();
- FieldViewsSpecHelpers.expectMessageContains(view, 'Unlinking');
- AjaxHelpers.expectRequest(requests, 'POST', data.disconnectUrl);
- AjaxHelpers.respondWithNoContent(requests);
-
- expect(view.$(selector).text().trim()).toBe('Link Your Account');
- FieldViewsSpecHelpers.expectMessageContains(view, 'Successfully unlinked.');
-
- view.$(selector).click();
- FieldViewsSpecHelpers.expectMessageContains(view, 'Linking');
- expect(view.redirect_to).toHaveBeenCalledWith(data.connectUrl);
- };
-
- return {
- verifyAuthField: verifyAuthField
- };
-});
diff --git a/lms/static/js/spec/student_account/account_settings_fields_spec.js b/lms/static/js/spec/student_account/account_settings_fields_spec.js
deleted file mode 100644
index 76ea7c512b7f..000000000000
--- a/lms/static/js/spec/student_account/account_settings_fields_spec.js
+++ /dev/null
@@ -1,216 +0,0 @@
-define(['backbone',
- 'jquery',
- 'underscore',
- 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
- 'common/js/spec_helpers/template_helpers',
- 'js/student_account/models/user_account_model',
- 'js/views/fields',
- 'js/spec/views/fields_helpers',
- 'js/spec/student_account/account_settings_fields_helpers',
- 'js/student_account/views/account_settings_fields',
- 'js/student_account/models/user_account_model',
- 'string_utils'],
-function(Backbone, $, _, AjaxHelpers, TemplateHelpers, UserAccountModel, FieldViews, FieldViewsSpecHelpers,
- AccountSettingsFieldViewSpecHelpers, AccountSettingsFieldViews) {
- 'use strict';
-
- describe('edx.AccountSettingsFieldViews', function() {
- var requests,
- timerCallback, // eslint-disable-line no-unused-vars
- data;
-
- beforeEach(function() {
- timerCallback = jasmine.createSpy('timerCallback');
- jasmine.clock().install();
- });
-
- afterEach(function() {
- jasmine.clock().uninstall();
- });
-
- it('sends request to reset password on clicking link in PasswordFieldView', function() {
- requests = AjaxHelpers.requests(this);
-
- var fieldData = FieldViewsSpecHelpers.createFieldData(AccountSettingsFieldViews.PasswordFieldView, {
- linkHref: '/password_reset',
- emailAttribute: 'email',
- valueAttribute: 'password'
- });
-
- var view = new AccountSettingsFieldViews.PasswordFieldView(fieldData).render();
- expect(view.$('.u-field-value > button').is(':disabled')).toBe(false);
- view.$('.u-field-value > button').click();
- expect(view.$('.u-field-value > button').is(':disabled')).toBe(true);
- AjaxHelpers.expectRequest(requests, 'POST', '/password_reset', 'email=legolas%40woodland.middlearth');
- AjaxHelpers.respondWithJson(requests, {success: 'true'});
- FieldViewsSpecHelpers.expectMessageContains(
- view,
- "We've sent a message to legolas@woodland.middlearth. "
- + 'Click the link in the message to reset your password.'
- );
- });
-
- it('update time zone dropdown after country dropdown changes', function() {
- var baseSelector = '.u-field-value > select';
- var groupsSelector = baseSelector + '> optgroup';
- var groupOptionsSelector = groupsSelector + '> option';
-
- var timeZoneData = FieldViewsSpecHelpers.createFieldData(AccountSettingsFieldViews.TimeZoneFieldView, {
- valueAttribute: 'time_zone',
- groupOptions: [{
- groupTitle: gettext('All Time Zones'),
- selectOptions: FieldViewsSpecHelpers.SELECT_OPTIONS,
- nullValueOptionLabel: 'Default (Local Time Zone)'
- }],
- persistChanges: true,
- required: true
- });
- var countryData = FieldViewsSpecHelpers.createFieldData(AccountSettingsFieldViews.DropdownFieldView, {
- valueAttribute: 'country',
- options: [['KY', 'Cayman Islands'], ['CA', 'Canada'], ['GY', 'Guyana']],
- persistChanges: true
- });
-
- var countryChange = {country: 'GY'};
- var timeZoneChange = {time_zone: 'Pacific/Kosrae'};
-
- var timeZoneView = new AccountSettingsFieldViews.TimeZoneFieldView(timeZoneData).render();
- var countryView = new AccountSettingsFieldViews.DropdownFieldView(countryData).render();
-
- requests = AjaxHelpers.requests(this);
-
- timeZoneView.listenToCountryView(countryView);
-
- // expect time zone dropdown to have single subheader ('All Time Zones')
- expect(timeZoneView.$(groupsSelector).length).toBe(1);
- expect(timeZoneView.$(groupOptionsSelector).length).toBe(3);
- expect(timeZoneView.$(groupOptionsSelector)[0].value).toBe(FieldViewsSpecHelpers.SELECT_OPTIONS[0][0]);
-
- // change country
- countryView.$(baseSelector).val(countryChange[countryData.valueAttribute]).change();
- countryView.$(baseSelector).focusout();
- FieldViewsSpecHelpers.expectAjaxRequestWithData(requests, countryChange);
- AjaxHelpers.respondWithJson(requests, {success: 'true'});
-
- AjaxHelpers.expectRequest(
- requests,
- 'GET',
- '/api/user/v1/preferences/time_zones/?country_code=GY'
- );
- AjaxHelpers.respondWithJson(requests, [
- {time_zone: 'America/Guyana', description: 'America/Guyana (ECT, UTC-0500)'},
- {time_zone: 'Pacific/Kosrae', description: 'Pacific/Kosrae (KOST, UTC+1100)'}
- ]);
-
- // expect time zone dropdown to have two subheaders (country/all time zone sub-headers) with new values
- expect(timeZoneView.$(groupsSelector).length).toBe(2);
- expect(timeZoneView.$(groupOptionsSelector).length).toBe(6);
- expect(timeZoneView.$(groupOptionsSelector)[0].value).toBe('America/Guyana');
-
- // select time zone option from option
- timeZoneView.$(baseSelector).val(timeZoneChange[timeZoneData.valueAttribute]).change();
- timeZoneView.$(baseSelector).focusout();
- FieldViewsSpecHelpers.expectAjaxRequestWithData(requests, timeZoneChange);
- AjaxHelpers.respondWithJson(requests, {success: 'true'});
- timeZoneView.render();
-
- // expect time zone dropdown to have three subheaders (currently selected/country/all time zones)
- expect(timeZoneView.$(groupsSelector).length).toBe(3);
- expect(timeZoneView.$(groupOptionsSelector).length).toBe(6);
- expect(timeZoneView.$(groupOptionsSelector)[0].value).toBe('Pacific/Kosrae');
- });
-
- it('sends request to /i18n/setlang/ after changing language in LanguagePreferenceFieldView', function() {
- requests = AjaxHelpers.requests(this);
-
- var selector = '.u-field-value > select';
- var fieldData = FieldViewsSpecHelpers.createFieldData(AccountSettingsFieldViews.DropdownFieldView, {
- valueAttribute: 'language',
- options: FieldViewsSpecHelpers.SELECT_OPTIONS,
- persistChanges: true
- });
-
- var view = new AccountSettingsFieldViews.LanguagePreferenceFieldView(fieldData).render();
-
- data = {language: FieldViewsSpecHelpers.SELECT_OPTIONS[2][0]};
- view.$(selector).val(data[fieldData.valueAttribute]).change();
- view.$(selector).focusout();
- FieldViewsSpecHelpers.expectAjaxRequestWithData(requests, data);
- AjaxHelpers.respondWithNoContent(requests);
-
- AjaxHelpers.expectRequest(
- requests,
- 'POST',
- '/i18n/setlang/',
- $.param({
- language: data[fieldData.valueAttribute],
- next: window.location.href
- })
- );
- // Django will actually respond with a 302 redirect, but that would cause a page load during these
- // unittests. 204 should work fine for testing.
- AjaxHelpers.respondWithNoContent(requests);
- FieldViewsSpecHelpers.expectMessageContains(view, 'Your changes have been saved.');
-
- data = {language: FieldViewsSpecHelpers.SELECT_OPTIONS[1][0]};
- view.$(selector).val(data[fieldData.valueAttribute]).change();
- view.$(selector).focusout();
- FieldViewsSpecHelpers.expectAjaxRequestWithData(requests, data);
- AjaxHelpers.respondWithNoContent(requests);
-
- AjaxHelpers.expectRequest(
- requests,
- 'POST',
- '/i18n/setlang/',
- $.param({
- language: data[fieldData.valueAttribute],
- next: window.location.href
- })
- );
- AjaxHelpers.respondWithError(requests, 500);
- FieldViewsSpecHelpers.expectMessageContains(
- view,
- 'You must sign out and sign back in before your language changes take effect.'
- );
- });
-
- it('reads and saves the value correctly for LanguageProficienciesFieldView', function() {
- requests = AjaxHelpers.requests(this);
-
- var selector = '.u-field-value > select';
- var fieldData = FieldViewsSpecHelpers.createFieldData(AccountSettingsFieldViews.DropdownFieldView, {
- valueAttribute: 'language_proficiencies',
- options: FieldViewsSpecHelpers.SELECT_OPTIONS,
- persistChanges: true
- });
- fieldData.model.set({language_proficiencies: [{code: FieldViewsSpecHelpers.SELECT_OPTIONS[0][0]}]});
-
- var view = new AccountSettingsFieldViews.LanguageProficienciesFieldView(fieldData).render();
-
- expect(view.modelValue()).toBe(FieldViewsSpecHelpers.SELECT_OPTIONS[0][0]);
-
- data = {language_proficiencies: [{code: FieldViewsSpecHelpers.SELECT_OPTIONS[1][0]}]};
- view.$(selector).val(FieldViewsSpecHelpers.SELECT_OPTIONS[1][0]).change();
- view.$(selector).focusout();
- FieldViewsSpecHelpers.expectAjaxRequestWithData(requests, data);
- AjaxHelpers.respondWithNoContent(requests);
- });
-
- it('correctly links and unlinks from AuthFieldView', function() {
- requests = AjaxHelpers.requests(this);
-
- var fieldData = FieldViewsSpecHelpers.createFieldData(FieldViews.LinkFieldView, {
- title: 'Yet another social network',
- helpMessage: '',
- valueAttribute: 'auth-yet-another',
- connected: true,
- acceptsLogins: 'true',
- connectUrl: 'yetanother.com/auth/connect',
- disconnectUrl: 'yetanother.com/auth/disconnect'
- });
- var view = new AccountSettingsFieldViews.AuthFieldView(fieldData).render();
-
- AccountSettingsFieldViewSpecHelpers.verifyAuthField(view, fieldData, requests);
- });
- });
-});
diff --git a/lms/static/js/spec/student_account/account_settings_view_spec.js b/lms/static/js/spec/student_account/account_settings_view_spec.js
deleted file mode 100644
index c0c213cf3c5c..000000000000
--- a/lms/static/js/spec/student_account/account_settings_view_spec.js
+++ /dev/null
@@ -1,91 +0,0 @@
-define(['backbone',
- 'jquery',
- 'underscore',
- 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
- 'common/js/spec_helpers/template_helpers',
- 'js/spec/student_account/helpers',
- 'js/views/fields',
- 'js/student_account/models/user_account_model',
- 'js/student_account/views/account_settings_view'
-],
-function(Backbone, $, _, AjaxHelpers, TemplateHelpers, Helpers, FieldViews, UserAccountModel,
- AccountSettingsView) {
- 'use strict';
-
- describe('edx.user.AccountSettingsView', function() {
- var createAccountSettingsView = function() {
- var model = new UserAccountModel();
- model.set(Helpers.createAccountSettingsData());
-
- var aboutSectionsData = [
- {
- title: 'Basic Account Information',
- messageType: 'info',
- message: 'Your profile settings are managed by Test Enterprise. '
- + 'Contact your administrator or edX Support for help.',
- fields: [
- {
- view: new FieldViews.ReadonlyFieldView({
- model: model,
- title: 'Username',
- valueAttribute: 'username'
- })
- },
- {
- view: new FieldViews.TextFieldView({
- model: model,
- title: 'Full Name',
- valueAttribute: 'name'
- })
- }
- ]
- },
- {
- title: 'Additional Information',
- fields: [
- {
- view: new FieldViews.DropdownFieldView({
- model: model,
- title: 'Education Completed',
- valueAttribute: 'level_of_education',
- options: Helpers.FIELD_OPTIONS
- })
- }
- ]
- }
- ];
-
- var accountSettingsView = new AccountSettingsView({
- el: $('.wrapper-account-settings'),
- model: model,
- tabSections: {
- aboutTabSections: aboutSectionsData
- }
- });
-
- return accountSettingsView;
- };
-
- beforeEach(function() {
- setFixtures('');
- });
-
- it('shows loading error correctly', function() {
- var accountSettingsView = createAccountSettingsView();
-
- accountSettingsView.render();
- Helpers.expectLoadingErrorIsVisible(accountSettingsView, false);
-
- accountSettingsView.showLoadingError();
- Helpers.expectLoadingErrorIsVisible(accountSettingsView, true);
- });
-
- it('renders all fields as expected', function() {
- var accountSettingsView = createAccountSettingsView();
-
- accountSettingsView.render();
- Helpers.expectLoadingErrorIsVisible(accountSettingsView, false);
- Helpers.expectSettingsSectionsAndFieldsToBeRendered(accountSettingsView);
- });
- });
-});
diff --git a/lms/static/js/student_account/views/account_section_view.js b/lms/static/js/student_account/views/account_section_view.js
deleted file mode 100644
index 70cd217477a4..000000000000
--- a/lms/static/js/student_account/views/account_section_view.js
+++ /dev/null
@@ -1,48 +0,0 @@
-// eslint-disable-next-line no-shadow-restricted-names
-(function(define, undefined) {
- 'use strict';
-
- define([
- 'gettext',
- 'jquery',
- 'underscore',
- 'backbone',
- 'edx-ui-toolkit/js/utils/html-utils',
- 'text!templates/student_account/account_settings_section.underscore'
- ], function(gettext, $, _, Backbone, HtmlUtils, sectionTemplate) {
- var AccountSectionView = Backbone.View.extend({
-
- initialize: function(options) {
- this.options = options;
- _.bindAll(this, 'render', 'renderFields');
- },
-
- render: function() {
- HtmlUtils.setHtml(
- this.$el,
- HtmlUtils.template(sectionTemplate)({
- HtmlUtils: HtmlUtils,
- sections: this.options.sections,
- tabName: this.options.tabName,
- tabLabel: this.options.tabLabel
- })
- );
-
- this.renderFields();
- },
-
- renderFields: function() {
- var view = this;
-
- _.each(view.$('.' + view.options.tabName + '-section-body'), function(sectionEl, index) {
- _.each(view.options.sections[index].fields, function(field) {
- $(sectionEl).append(field.view.render().el);
- });
- });
- return this;
- }
- });
-
- return AccountSectionView;
- });
-}).call(this, define || RequireJS.define);
diff --git a/lms/static/js/student_account/views/account_settings_factory.js b/lms/static/js/student_account/views/account_settings_factory.js
deleted file mode 100644
index 70d3ad205c10..000000000000
--- a/lms/static/js/student_account/views/account_settings_factory.js
+++ /dev/null
@@ -1,495 +0,0 @@
-// eslint-disable-next-line no-shadow-restricted-names
-(function(define, undefined) {
- 'use strict';
-
- define([
- 'gettext', 'jquery', 'underscore', 'backbone', 'logger',
- 'js/student_account/models/user_account_model',
- 'js/student_account/models/user_preferences_model',
- 'js/student_account/views/account_settings_fields',
- 'js/student_account/views/account_settings_view',
- 'edx-ui-toolkit/js/utils/string-utils',
- 'edx-ui-toolkit/js/utils/html-utils'
- ], function(gettext, $, _, Backbone, Logger, UserAccountModel, UserPreferencesModel,
- AccountSettingsFieldViews, AccountSettingsView, StringUtils, HtmlUtils) {
- return function(
- fieldsData,
- disableOrderHistoryTab,
- ordersHistoryData,
- authData,
- passwordResetSupportUrl,
- userAccountsApiUrl,
- userPreferencesApiUrl,
- accountUserId,
- platformName,
- contactEmail,
- allowEmailChange,
- enableCoppaCompliance,
- socialPlatforms,
- syncLearnerProfileData,
- enterpriseName,
- enterpriseReadonlyAccountFields,
- edxSupportUrl,
- extendedProfileFields,
- displayAccountDeletion,
- isSecondaryEmailFeatureEnabled,
- betaLanguage
- ) {
- var $accountSettingsElement, userAccountModel, userPreferencesModel, aboutSectionsData,
- accountsSectionData, ordersSectionData, accountSettingsView, showAccountSettingsPage,
- showLoadingError, orderNumber, getUserField, userFields, timeZoneDropdownField, countryDropdownField,
- emailFieldView, secondaryEmailFieldView, socialFields, accountDeletionFields, platformData,
- aboutSectionMessageType, aboutSectionMessage, fullnameFieldView, countryFieldView,
- fullNameFieldData, emailFieldData, secondaryEmailFieldData, countryFieldData, additionalFields,
- fieldItem, emailFieldViewIndex, focusId, yearOfBirthViewIndex, levelOfEducationFieldData,
- tabIndex = 0;
-
- $accountSettingsElement = $('.wrapper-account-settings');
-
- userAccountModel = new UserAccountModel();
- userAccountModel.url = userAccountsApiUrl;
-
- userPreferencesModel = new UserPreferencesModel();
- userPreferencesModel.url = userPreferencesApiUrl;
-
- if (syncLearnerProfileData && enterpriseName) {
- aboutSectionMessageType = 'info';
- aboutSectionMessage = HtmlUtils.interpolateHtml(
- gettext('Your profile settings are managed by {enterprise_name}. Contact your administrator or {link_start}edX Support{link_end} for help.'), // eslint-disable-line max-len
- {
- enterprise_name: enterpriseName,
- link_start: HtmlUtils.HTML(
- StringUtils.interpolate(
- '', {
- edx_support_url: edxSupportUrl
- }
- )
- ),
- link_end: HtmlUtils.HTML('')
- }
- );
- }
-
- emailFieldData = {
- model: userAccountModel,
- title: gettext('Email Address (Sign In)'),
- valueAttribute: 'email',
- helpMessage: StringUtils.interpolate(
- gettext('You receive messages from {platform_name} and course teams at this address.'), // eslint-disable-line max-len
- {platform_name: platformName}
- ),
- persistChanges: true
- };
- if (!allowEmailChange || (syncLearnerProfileData && enterpriseReadonlyAccountFields.fields.indexOf('email') !== -1)) { // eslint-disable-line max-len
- emailFieldView = {
- view: new AccountSettingsFieldViews.ReadonlyFieldView(emailFieldData)
- };
- } else {
- emailFieldView = {
- view: new AccountSettingsFieldViews.EmailFieldView(emailFieldData)
- };
- }
-
- secondaryEmailFieldData = {
- model: userAccountModel,
- title: gettext('Recovery Email Address'),
- valueAttribute: 'secondary_email',
- helpMessage: gettext('You may access your account with this address if single-sign on or access to your primary email is not available.'), // eslint-disable-line max-len
- persistChanges: true
- };
-
- fullNameFieldData = {
- model: userAccountModel,
- title: gettext('Full Name'),
- valueAttribute: 'name',
- helpMessage: gettext('The name that is used for ID verification and that appears on your certificates.'), // eslint-disable-line max-len,
- persistChanges: true
- };
- if (syncLearnerProfileData && enterpriseReadonlyAccountFields.fields.indexOf('name') !== -1) {
- fullnameFieldView = {
- view: new AccountSettingsFieldViews.ReadonlyFieldView(fullNameFieldData)
- };
- } else {
- fullnameFieldView = {
- view: new AccountSettingsFieldViews.TextFieldView(fullNameFieldData)
- };
- }
-
- countryFieldData = {
- model: userAccountModel,
- required: true,
- title: gettext('Country or Region of Residence'),
- valueAttribute: 'country',
- options: fieldsData.country.options,
- persistChanges: true,
- helpMessage: gettext('The country or region where you live.')
- };
- if (syncLearnerProfileData && enterpriseReadonlyAccountFields.fields.indexOf('country') !== -1) {
- countryFieldData.editable = 'never';
- countryFieldView = {
- view: new AccountSettingsFieldViews.DropdownFieldView(
- countryFieldData
- )
- };
- } else {
- countryFieldView = {
- view: new AccountSettingsFieldViews.DropdownFieldView(countryFieldData)
- };
- }
-
- levelOfEducationFieldData = fieldsData.level_of_education.options;
- if (enableCoppaCompliance) {
- levelOfEducationFieldData = levelOfEducationFieldData.filter(option => option[0] !== 'el');
- }
-
- aboutSectionsData = [
- {
- title: gettext('Basic Account Information'),
- subtitle: gettext('These settings include basic information about your account.'),
-
- messageType: aboutSectionMessageType,
- message: aboutSectionMessage,
-
- fields: [
- {
- view: new AccountSettingsFieldViews.ReadonlyFieldView({
- model: userAccountModel,
- title: gettext('Username'),
- valueAttribute: 'username',
- helpMessage: StringUtils.interpolate(
- gettext('The name that identifies you on {platform_name}. You cannot change your username.'), // eslint-disable-line max-len
- {platform_name: platformName}
- )
- })
- },
- fullnameFieldView,
- emailFieldView,
- {
- view: new AccountSettingsFieldViews.PasswordFieldView({
- model: userAccountModel,
- title: gettext('Password'),
- screenReaderTitle: gettext('Reset Your Password'),
- valueAttribute: 'password',
- emailAttribute: 'email',
- passwordResetSupportUrl: passwordResetSupportUrl,
- linkTitle: gettext('Reset Your Password'),
- linkHref: fieldsData.password.url,
- helpMessage: gettext('Check your email account for instructions to reset your password.') // eslint-disable-line max-len
- })
- },
- {
- view: new AccountSettingsFieldViews.LanguagePreferenceFieldView({
- model: userPreferencesModel,
- title: gettext('Language'),
- valueAttribute: 'pref-lang',
- required: true,
- refreshPageOnSave: true,
- helpMessage: StringUtils.interpolate(
- gettext('The language used throughout this site. This site is currently available in a limited number of languages. Changing the value of this field will cause the page to refresh.'), // eslint-disable-line max-len
- {platform_name: platformName}
- ),
- options: fieldsData.language.options,
- persistChanges: true,
- focusNextID: '#u-field-select-country'
- })
- },
- countryFieldView,
- {
- view: new AccountSettingsFieldViews.TimeZoneFieldView({
- model: userPreferencesModel,
- required: true,
- title: gettext('Time Zone'),
- valueAttribute: 'time_zone',
- helpMessage: gettext('Select the time zone for displaying course dates. If you do not specify a time zone, course dates, including assignment deadlines, will be displayed in your browser\'s local time zone.'), // eslint-disable-line max-len
- groupOptions: [{
- groupTitle: gettext('All Time Zones'),
- selectOptions: fieldsData.time_zone.options,
- nullValueOptionLabel: gettext('Default (Local Time Zone)')
- }],
- persistChanges: true
- })
- }
- ]
- },
- {
- title: gettext('Additional Information'),
- fields: [
- {
- view: new AccountSettingsFieldViews.DropdownFieldView({
- model: userAccountModel,
- title: gettext('Education Completed'),
- valueAttribute: 'level_of_education',
- options: levelOfEducationFieldData,
- persistChanges: true
- })
- },
- {
- view: new AccountSettingsFieldViews.DropdownFieldView({
- model: userAccountModel,
- title: gettext('Gender'),
- valueAttribute: 'gender',
- options: fieldsData.gender.options,
- persistChanges: true
- })
- },
- {
- view: new AccountSettingsFieldViews.DropdownFieldView({
- model: userAccountModel,
- title: gettext('Year of Birth'),
- valueAttribute: 'year_of_birth',
- options: fieldsData.year_of_birth.options,
- persistChanges: true
- })
- },
- {
- view: new AccountSettingsFieldViews.LanguageProficienciesFieldView({
- model: userAccountModel,
- title: gettext('Preferred Language'),
- valueAttribute: 'language_proficiencies',
- options: fieldsData.preferred_language.options,
- persistChanges: true
- })
- }
- ]
- }
- ];
-
- if (enableCoppaCompliance) {
- yearOfBirthViewIndex = aboutSectionsData[1].fields.findIndex(function(field) {
- return field.view.options.valueAttribute === 'year_of_birth';
- });
- aboutSectionsData[1].fields.splice(yearOfBirthViewIndex, 1);
- }
-
- // Secondary email address
- if (isSecondaryEmailFeatureEnabled) {
- secondaryEmailFieldView = {
- view: new AccountSettingsFieldViews.EmailFieldView(secondaryEmailFieldData),
- successMessage: function() {
- return HtmlUtils.joinHtml(
- this.indicators.success,
- StringUtils.interpolate(
- gettext('We\'ve sent a confirmation message to {new_secondary_email_address}. Click the link in the message to update your secondary email address.'), // eslint-disable-line max-len
- {
- new_secondary_email_address: this.fieldValue()
- }
- )
- );
- }
- };
- emailFieldViewIndex = aboutSectionsData[0].fields.indexOf(emailFieldView);
-
- // Insert secondary email address after email address field.
- aboutSectionsData[0].fields.splice(
- emailFieldViewIndex + 1, 0, secondaryEmailFieldView
- );
- }
-
- // Add the extended profile fields
- additionalFields = aboutSectionsData[1];
- for (var field in extendedProfileFields) { // eslint-disable-line guard-for-in, no-restricted-syntax, vars-on-top, max-len
- fieldItem = extendedProfileFields[field];
- if (fieldItem.field_type === 'TextField') {
- additionalFields.fields.push({
- view: new AccountSettingsFieldViews.ExtendedFieldTextFieldView({
- model: userAccountModel,
- title: fieldItem.field_label,
- fieldName: fieldItem.field_name,
- valueAttribute: 'extended_profile',
- persistChanges: true
- })
- });
- } else {
- if (fieldItem.field_type === 'ListField') {
- additionalFields.fields.push({
- view: new AccountSettingsFieldViews.ExtendedFieldListFieldView({
- model: userAccountModel,
- title: fieldItem.field_label,
- fieldName: fieldItem.field_name,
- options: fieldItem.field_options,
- valueAttribute: 'extended_profile',
- persistChanges: true
- })
- });
- }
- }
- }
-
- // Add the social link fields
- socialFields = {
- title: gettext('Social Media Links'),
- subtitle: gettext('Optionally, link your personal accounts to the social media icons on your edX profile.'), // eslint-disable-line max-len
- fields: []
- };
-
- for (var socialPlatform in socialPlatforms) { // eslint-disable-line guard-for-in, no-restricted-syntax, vars-on-top, max-len
- platformData = socialPlatforms[socialPlatform];
- socialFields.fields.push(
- {
- view: new AccountSettingsFieldViews.SocialLinkTextFieldView({
- model: userAccountModel,
- title: StringUtils.interpolate(
- gettext('{platform_display_name} Link'),
- {platform_display_name: platformData.display_name}
- ),
- valueAttribute: 'social_links',
- helpMessage: StringUtils.interpolate(
- gettext('Enter your {platform_display_name} username or the URL to your {platform_display_name} page. Delete the URL to remove the link.'), // eslint-disable-line max-len
- {platform_display_name: platformData.display_name}
- ),
- platform: socialPlatform,
- persistChanges: true,
- placeholder: platformData.example
- })
- }
- );
- }
- aboutSectionsData.push(socialFields);
-
- // Add account deletion fields
- if (displayAccountDeletion) {
- accountDeletionFields = {
- title: gettext('Delete My Account'),
- fields: [],
- // Used so content can be rendered external to Backbone
- domHookId: 'account-deletion-container'
- };
- aboutSectionsData.push(accountDeletionFields);
- }
-
- // set TimeZoneField to listen to CountryField
-
- getUserField = function(list, search) {
- // eslint-disable-next-line no-shadow
- return _.find(list, function(field) {
- return field.view.options.valueAttribute === search;
- }).view;
- };
- userFields = _.find(aboutSectionsData, function(section) {
- return section.title === gettext('Basic Account Information');
- }).fields;
- timeZoneDropdownField = getUserField(userFields, 'time_zone');
- countryDropdownField = getUserField(userFields, 'country');
- timeZoneDropdownField.listenToCountryView(countryDropdownField);
-
- accountsSectionData = [
- {
- title: gettext('Linked Accounts'),
- subtitle: StringUtils.interpolate(
- gettext('You can link your social media accounts to simplify signing in to {platform_name}.'),
- {platform_name: platformName}
- ),
- fields: _.map(authData.providers, function(provider) {
- return {
- view: new AccountSettingsFieldViews.AuthFieldView({
- title: provider.name,
- valueAttribute: 'auth-' + provider.id,
- helpMessage: '',
- connected: provider.connected,
- connectUrl: provider.connect_url,
- acceptsLogins: provider.accepts_logins,
- disconnectUrl: provider.disconnect_url,
- platformName: platformName
- })
- };
- })
- }
- ];
-
- ordersHistoryData.unshift(
- {
- title: gettext('ORDER NAME'),
- order_date: gettext('ORDER PLACED'),
- price: gettext('TOTAL'),
- number: gettext('ORDER NUMBER')
- }
- );
-
- ordersSectionData = [
- {
- title: gettext('My Orders'),
- subtitle: StringUtils.interpolate(
- gettext('This page contains information about orders that you have placed with {platform_name}.'), // eslint-disable-line max-len
- {platform_name: platformName}
- ),
- fields: _.map(ordersHistoryData, function(order) {
- orderNumber = order.number;
- if (orderNumber === 'ORDER NUMBER') {
- orderNumber = 'orderId';
- }
- return {
- view: new AccountSettingsFieldViews.OrderHistoryFieldView({
- totalPrice: order.price,
- orderId: order.number,
- orderDate: order.order_date,
- receiptUrl: order.receipt_url,
- valueAttribute: 'order-' + orderNumber,
- lines: order.lines
- })
- };
- })
- }
- ];
-
- accountSettingsView = new AccountSettingsView({
- model: userAccountModel,
- accountUserId: accountUserId,
- el: $accountSettingsElement,
- tabSections: {
- aboutTabSections: aboutSectionsData,
- accountsTabSections: accountsSectionData,
- ordersTabSections: ordersSectionData
- },
- userPreferencesModel: userPreferencesModel,
- disableOrderHistoryTab: disableOrderHistoryTab,
- betaLanguage: betaLanguage
- });
-
- accountSettingsView.render();
- focusId = $.cookie('focus_id');
- if (focusId) {
- // eslint-disable-next-line no-bitwise
- if (~focusId.indexOf('beta-language')) {
- tabIndex = -1;
-
- // Scroll to top of selected element
- $('html, body').animate({
- scrollTop: $(focusId).offset().top
- }, 'slow');
- }
- $(focusId).attr({tabindex: tabIndex}).focus();
- // Deleting the cookie
- document.cookie = 'focus_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/account;';
- }
- showAccountSettingsPage = function() {
- // Record that the account settings page was viewed.
- Logger.log('edx.user.settings.viewed', {
- page: 'account',
- visibility: null,
- user_id: accountUserId
- });
- };
-
- showLoadingError = function() {
- accountSettingsView.showLoadingError();
- };
-
- userAccountModel.fetch({
- success: function() {
- // Fetch the user preferences model
- userPreferencesModel.fetch({
- success: showAccountSettingsPage,
- error: showLoadingError
- });
- },
- error: showLoadingError
- });
-
- return {
- userAccountModel: userAccountModel,
- userPreferencesModel: userPreferencesModel,
- accountSettingsView: accountSettingsView
- };
- };
- });
-}).call(this, define || RequireJS.define);
diff --git a/lms/static/js/student_account/views/account_settings_fields.js b/lms/static/js/student_account/views/account_settings_fields.js
deleted file mode 100644
index 1fc174f93588..000000000000
--- a/lms/static/js/student_account/views/account_settings_fields.js
+++ /dev/null
@@ -1,466 +0,0 @@
-// eslint-disable-next-line no-shadow-restricted-names
-(function(define, undefined) {
- 'use strict';
-
- define([
- 'gettext',
- 'jquery',
- 'underscore',
- 'backbone',
- 'js/views/fields',
- 'text!templates/fields/field_text_account.underscore',
- 'text!templates/fields/field_readonly_account.underscore',
- 'text!templates/fields/field_link_account.underscore',
- 'text!templates/fields/field_dropdown_account.underscore',
- 'text!templates/fields/field_social_link_account.underscore',
- 'text!templates/fields/field_order_history.underscore',
- 'edx-ui-toolkit/js/utils/string-utils',
- 'edx-ui-toolkit/js/utils/html-utils'
- ], function(
- gettext, $, _, Backbone,
- FieldViews,
- field_text_account_template,
- field_readonly_account_template,
- field_link_account_template,
- field_dropdown_account_template,
- field_social_link_template,
- field_order_history_template,
- StringUtils,
- HtmlUtils
- ) {
- var AccountSettingsFieldViews = {
- ReadonlyFieldView: FieldViews.ReadonlyFieldView.extend({
- fieldTemplate: field_readonly_account_template
- }),
- TextFieldView: FieldViews.TextFieldView.extend({
- fieldTemplate: field_text_account_template
- }),
- DropdownFieldView: FieldViews.DropdownFieldView.extend({
- fieldTemplate: field_dropdown_account_template
- }),
- EmailFieldView: FieldViews.TextFieldView.extend({
- fieldTemplate: field_text_account_template,
- successMessage: function() {
- return HtmlUtils.joinHtml(
- this.indicators.success,
- StringUtils.interpolate(
- gettext('We\'ve sent a confirmation message to {new_email_address}. Click the link in the message to update your email address.'), // eslint-disable-line max-len
- {new_email_address: this.fieldValue()}
- )
- );
- }
- }),
- LanguagePreferenceFieldView: FieldViews.DropdownFieldView.extend({
- fieldTemplate: field_dropdown_account_template,
-
- initialize: function(options) {
- this._super(options); // eslint-disable-line no-underscore-dangle
- this.listenTo(this.model, 'revertValue', this.revertValue);
- },
-
- revertValue: function(event) {
- var attributes = {},
- oldPrefLang = $(event.target).data('old-lang-code');
-
- if (oldPrefLang) {
- attributes['pref-lang'] = oldPrefLang;
- this.saveAttributes(attributes);
- }
- },
-
- saveSucceeded: function() {
- var data = {
- language: this.modelValue(),
- next: window.location.href
- };
-
- var view = this;
- $.ajax({
- type: 'POST',
- url: '/i18n/setlang/',
- data: data,
- dataType: 'html',
- success: function() {
- view.showSuccessMessage();
- },
- error: function() {
- view.showNotificationMessage(
- HtmlUtils.joinHtml(
- view.indicators.error,
- gettext('You must sign out and sign back in before your language changes take effect.') // eslint-disable-line max-len
- )
- );
- }
- });
- }
-
- }),
- TimeZoneFieldView: FieldViews.DropdownFieldView.extend({
- fieldTemplate: field_dropdown_account_template,
-
- initialize: function(options) {
- this.options = _.extend({}, options);
- _.bindAll(this, 'listenToCountryView', 'updateCountrySubheader', 'replaceOrAddGroupOption');
- this._super(options); // eslint-disable-line no-underscore-dangle
- },
-
- listenToCountryView: function(view) {
- this.listenTo(view.model, 'change:country', this.updateCountrySubheader);
- },
-
- updateCountrySubheader: function(user) {
- var view = this;
- $.ajax({
- type: 'GET',
- url: '/api/user/v1/preferences/time_zones/',
- data: {country_code: user.attributes.country},
- success: function(data) {
- var countryTimeZones = $.map(data, function(timeZoneInfo) {
- return [[timeZoneInfo.time_zone, timeZoneInfo.description]];
- });
- view.replaceOrAddGroupOption(
- 'Country Time Zones',
- countryTimeZones
- );
- view.render();
- }
- });
- },
-
- updateValueInField: function() {
- var options;
- if (this.modelValue()) {
- options = [[this.modelValue(), this.displayValue(this.modelValue())]];
- this.replaceOrAddGroupOption(
- 'Currently Selected Time Zone',
- options
- );
- }
- this._super(); // eslint-disable-line no-underscore-dangle
- },
-
- replaceOrAddGroupOption: function(title, options) {
- var groupOption = {
- groupTitle: gettext(title),
- selectOptions: options
- };
-
- var index = _.findIndex(this.options.groupOptions, function(group) {
- return group.groupTitle === gettext(title);
- });
- if (index >= 0) {
- this.options.groupOptions[index] = groupOption;
- } else {
- this.options.groupOptions.unshift(groupOption);
- }
- }
-
- }),
- PasswordFieldView: FieldViews.LinkFieldView.extend({
- fieldType: 'button',
- fieldTemplate: field_link_account_template,
- events: {
- 'click button': 'linkClicked'
- },
- initialize: function(options) {
- this.options = _.extend({}, options);
- this._super(options);
- _.bindAll(this, 'resetPassword');
- },
- linkClicked: function(event) {
- event.preventDefault();
- this.toggleDisableButton(true);
- this.resetPassword(event);
- },
- resetPassword: function() {
- var data = {};
- data[this.options.emailAttribute] = this.model.get(this.options.emailAttribute);
-
- var view = this;
- $.ajax({
- type: 'POST',
- url: view.options.linkHref,
- data: data,
- success: function() {
- view.showSuccessMessage();
- view.setMessageTimeout();
- },
- error: function(xhr) {
- view.showErrorMessage(xhr);
- view.setMessageTimeout();
- view.toggleDisableButton(false);
- }
- });
- },
- toggleDisableButton: function(disabled) {
- var button = this.$('#u-field-link-' + this.options.valueAttribute);
- if (button) {
- button.prop('disabled', disabled);
- }
- },
- setMessageTimeout: function() {
- var view = this;
- setTimeout(function() {
- view.showHelpMessage();
- }, 6000);
- },
- successMessage: function() {
- return HtmlUtils.joinHtml(
- this.indicators.success,
- HtmlUtils.interpolateHtml(
- gettext('We\'ve sent a message to {email}. Click the link in the message to reset your password. Didn\'t receive the message? Contact {anchorStart}technical support{anchorEnd}.'), // eslint-disable-line max-len
- {
- email: this.model.get(this.options.emailAttribute),
- anchorStart: HtmlUtils.HTML(
- StringUtils.interpolate(
- '', {
- passwordResetSupportUrl: this.options.passwordResetSupportUrl
- }
- )
- ),
- anchorEnd: HtmlUtils.HTML('')
- }
- )
- );
- }
- }),
- LanguageProficienciesFieldView: FieldViews.DropdownFieldView.extend({
- fieldTemplate: field_dropdown_account_template,
- modelValue: function() {
- var modelValue = this.model.get(this.options.valueAttribute);
- if (_.isArray(modelValue) && modelValue.length > 0) {
- return modelValue[0].code;
- } else {
- return null;
- }
- },
- saveValue: function() {
- var attributes = {},
- value = '';
- if (this.persistChanges === true) {
- value = this.fieldValue() ? [{code: this.fieldValue()}] : [];
- attributes[this.options.valueAttribute] = value;
- this.saveAttributes(attributes);
- }
- }
- }),
- SocialLinkTextFieldView: FieldViews.TextFieldView.extend({
- render: function() {
- HtmlUtils.setHtml(this.$el, HtmlUtils.template(field_text_account_template)({
- id: this.options.valueAttribute + '_' + this.options.platform,
- title: this.options.title,
- value: this.modelValue(),
- message: this.options.helpMessage,
- placeholder: this.options.placeholder || ''
- }));
- this.delegateEvents();
- return this;
- },
-
- modelValue: function() {
- var socialLinks = this.model.get(this.options.valueAttribute);
- for (var i = 0; i < socialLinks.length; i++) { // eslint-disable-line vars-on-top
- if (socialLinks[i].platform === this.options.platform) {
- return socialLinks[i].social_link;
- }
- }
- return null;
- },
- saveValue: function() {
- var attributes, value;
- if (this.persistChanges === true) {
- attributes = {};
- value = this.fieldValue() != null ? [{
- platform: this.options.platform,
- social_link: this.fieldValue()
- }] : [];
- attributes[this.options.valueAttribute] = value;
- this.saveAttributes(attributes);
- }
- }
- }),
- ExtendedFieldTextFieldView: FieldViews.TextFieldView.extend({
- render: function() {
- HtmlUtils.setHtml(this.$el, HtmlUtils.template(field_text_account_template)({
- id: this.options.valueAttribute + '_' + this.options.field_name,
- title: this.options.title,
- value: this.modelValue(),
- message: this.options.helpMessage,
- placeholder: this.options.placeholder || ''
- }));
- this.delegateEvents();
- return this;
- },
-
- modelValue: function() {
- var extendedProfileFields = this.model.get(this.options.valueAttribute);
- for (var i = 0; i < extendedProfileFields.length; i++) { // eslint-disable-line vars-on-top
- if (extendedProfileFields[i].field_name === this.options.fieldName) {
- return extendedProfileFields[i].field_value;
- }
- }
- return null;
- },
- saveValue: function() {
- var attributes, value;
- if (this.persistChanges === true) {
- attributes = {};
- value = this.fieldValue() != null ? [{
- field_name: this.options.fieldName,
- field_value: this.fieldValue()
- }] : [];
- attributes[this.options.valueAttribute] = value;
- this.saveAttributes(attributes);
- }
- }
- }),
- ExtendedFieldListFieldView: FieldViews.DropdownFieldView.extend({
- fieldTemplate: field_dropdown_account_template,
- modelValue: function() {
- var extendedProfileFields = this.model.get(this.options.valueAttribute);
- for (var i = 0; i < extendedProfileFields.length; i++) { // eslint-disable-line vars-on-top
- if (extendedProfileFields[i].field_name === this.options.fieldName) {
- return extendedProfileFields[i].field_value;
- }
- }
- return null;
- },
- saveValue: function() {
- var attributes = {},
- value;
- if (this.persistChanges === true) {
- value = this.fieldValue() ? [{
- field_name: this.options.fieldName,
- field_value: this.fieldValue()
- }] : [];
- attributes[this.options.valueAttribute] = value;
- this.saveAttributes(attributes);
- }
- }
- }),
- AuthFieldView: FieldViews.LinkFieldView.extend({
- fieldTemplate: field_social_link_template,
- className: function() {
- return 'u-field u-field-social u-field-' + this.options.valueAttribute;
- },
- initialize: function(options) {
- this.options = _.extend({}, options);
- this._super(options);
- _.bindAll(this, 'redirect_to', 'disconnect', 'successMessage', 'inProgressMessage');
- },
- render: function() {
- var linkTitle = '',
- linkClass = '',
- subTitle = '',
- screenReaderTitle = StringUtils.interpolate(
- gettext('Link your {accountName} account'),
- {accountName: this.options.title}
- );
- if (this.options.connected) {
- linkTitle = gettext('Unlink This Account');
- linkClass = 'social-field-linked';
- subTitle = StringUtils.interpolate(
- gettext('You can use your {accountName} account to sign in to your {platformName} account.'), // eslint-disable-line max-len
- {accountName: this.options.title, platformName: this.options.platformName}
- );
- screenReaderTitle = StringUtils.interpolate(
- gettext('Unlink your {accountName} account'),
- {accountName: this.options.title}
- );
- } else if (this.options.acceptsLogins) {
- linkTitle = gettext('Link Your Account');
- linkClass = 'social-field-unlinked';
- subTitle = StringUtils.interpolate(
- gettext('Link your {accountName} account to your {platformName} account and use {accountName} to sign in to {platformName}.'), // eslint-disable-line max-len
- {accountName: this.options.title, platformName: this.options.platformName}
- );
- }
-
- HtmlUtils.setHtml(this.$el, HtmlUtils.template(this.fieldTemplate)({
- id: this.options.valueAttribute,
- title: this.options.title,
- screenReaderTitle: screenReaderTitle,
- linkTitle: linkTitle,
- subTitle: subTitle,
- linkClass: linkClass,
- linkHref: '#',
- message: this.helpMessage
- }));
- this.delegateEvents();
- return this;
- },
- linkClicked: function(event) {
- event.preventDefault();
-
- this.showInProgressMessage();
-
- if (this.options.connected) {
- this.disconnect();
- } else {
- // Direct the user to the providers site to start the authentication process.
- // See python-social-auth docs for more information.
- this.redirect_to(this.options.connectUrl);
- }
- },
- redirect_to: function(url) {
- window.location.href = url;
- },
- disconnect: function() {
- var data = {};
-
- // Disconnects the provider from the user's edX account.
- // See python-social-auth docs for more information.
- var view = this;
- $.ajax({
- type: 'POST',
- url: this.options.disconnectUrl,
- data: data,
- dataType: 'html',
- success: function() {
- view.options.connected = false;
- view.render();
- view.showSuccessMessage();
- },
- error: function(xhr) {
- view.showErrorMessage(xhr);
- }
- });
- },
- inProgressMessage: function() {
- return HtmlUtils.joinHtml(this.indicators.inProgress, (
- this.options.connected ? gettext('Unlinking') : gettext('Linking')
- ));
- },
- successMessage: function() {
- return HtmlUtils.joinHtml(this.indicators.success, gettext('Successfully unlinked.'));
- }
- }),
-
- OrderHistoryFieldView: FieldViews.ReadonlyFieldView.extend({
- fieldType: 'orderHistory',
- fieldTemplate: field_order_history_template,
-
- initialize: function(options) {
- this.options = options;
- this._super(options);
- this.template = HtmlUtils.template(this.fieldTemplate);
- },
-
- render: function() {
- HtmlUtils.setHtml(this.$el, this.template({
- totalPrice: this.options.totalPrice,
- orderId: this.options.orderId,
- orderDate: this.options.orderDate,
- receiptUrl: this.options.receiptUrl,
- valueAttribute: this.options.valueAttribute,
- lines: this.options.lines
- }));
- this.delegateEvents();
- return this;
- }
- })
- };
-
- return AccountSettingsFieldViews;
- });
-}).call(this, define || RequireJS.define);
diff --git a/lms/static/js/student_account/views/account_settings_view.js b/lms/static/js/student_account/views/account_settings_view.js
deleted file mode 100644
index 6ee9c9101d6c..000000000000
--- a/lms/static/js/student_account/views/account_settings_view.js
+++ /dev/null
@@ -1,157 +0,0 @@
-// eslint-disable-next-line no-shadow-restricted-names
-(function(define, undefined) {
- 'use strict';
-
- define([
- 'gettext',
- 'jquery',
- 'underscore',
- 'common/js/components/views/tabbed_view',
- 'edx-ui-toolkit/js/utils/html-utils',
- 'js/student_account/views/account_section_view',
- 'text!templates/student_account/account_settings.underscore'
- ], function(gettext, $, _, TabbedView, HtmlUtils, AccountSectionView, accountSettingsTemplate) {
- var AccountSettingsView = TabbedView.extend({
-
- navLink: '.account-nav-link',
- activeTab: 'aboutTabSections',
- events: {
- 'click .account-nav-link': 'switchTab',
- 'keydown .account-nav-link': 'keydownHandler',
- 'click .btn-alert-primary': 'revertValue'
- },
-
- initialize: function(options) {
- this.options = options;
- _.bindAll(this, 'render', 'switchTab', 'setActiveTab', 'showLoadingError');
- },
-
- render: function() {
- var tabName, betaLangMessage, helpTranslateText, helpTranslateLink, betaLangCode, oldLangCode,
- view = this;
- var accountSettingsTabs = [
- {
- name: 'aboutTabSections',
- id: 'about-tab',
- label: gettext('Account Information'),
- class: 'active',
- tabindex: 0,
- selected: true,
- expanded: true
- },
- {
- name: 'accountsTabSections',
- id: 'accounts-tab',
- label: gettext('Linked Accounts'),
- tabindex: -1,
- selected: false,
- expanded: false
- }
- ];
- if (!view.options.disableOrderHistoryTab) {
- accountSettingsTabs.push({
- name: 'ordersTabSections',
- id: 'orders-tab',
- label: gettext('Order History'),
- tabindex: -1,
- selected: false,
- expanded: false
- });
- }
-
- if (!_.isEmpty(view.options.betaLanguage) && $.cookie('old-pref-lang')) {
- betaLangMessage = HtmlUtils.interpolateHtml(
- gettext('You have set your language to {beta_language}, which is currently not fully translated. You can help us translate this language fully by joining the Transifex community and adding translations from English for learners that speak {beta_language}.'), // eslint-disable-line max-len
- {
- beta_language: view.options.betaLanguage.name
- }
- );
- helpTranslateText = HtmlUtils.interpolateHtml(
- gettext('Help Translate into {beta_language}'),
- {
- beta_language: view.options.betaLanguage.name
- }
- );
- betaLangCode = this.options.betaLanguage.code.split('-');
- if (betaLangCode.length > 1) {
- betaLangCode = betaLangCode[0] + '_' + betaLangCode[1].toUpperCase();
- } else {
- betaLangCode = betaLangCode[0];
- }
- helpTranslateLink = 'https://www.transifex.com/open-edx/edx-platform/translate/#' + betaLangCode;
- oldLangCode = $.cookie('old-pref-lang');
- // Deleting the cookie
- document.cookie = 'old-pref-lang=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/account;';
-
- $.cookie('focus_id', '#beta-language-message');
- }
- HtmlUtils.setHtml(this.$el, HtmlUtils.template(accountSettingsTemplate)({
- accountSettingsTabs: accountSettingsTabs,
- HtmlUtils: HtmlUtils,
- message: betaLangMessage,
- helpTranslateText: helpTranslateText,
- helpTranslateLink: helpTranslateLink,
- oldLangCode: oldLangCode
- }));
- _.each(accountSettingsTabs, function(tab) {
- tabName = tab.name;
- view.renderSection(view.options.tabSections[tabName], tabName, tab.label);
- });
- return this;
- },
-
- switchTab: function(e) {
- var $currentTab,
- $accountNavLink = $('.account-nav-link');
-
- if (e) {
- e.preventDefault();
- $currentTab = $(e.target);
- this.activeTab = $currentTab.data('name');
-
- _.each(this.$('.account-settings-tabpanels'), function(tabPanel) {
- $(tabPanel).addClass('hidden');
- });
-
- $('#' + this.activeTab + '-tabpanel').removeClass('hidden');
-
- $accountNavLink.attr('tabindex', -1);
- $accountNavLink.attr('aria-selected', false);
- $accountNavLink.attr('aria-expanded', false);
-
- $currentTab.attr('tabindex', 0);
- $currentTab.attr('aria-selected', true);
- $currentTab.attr('aria-expanded', true);
-
- $(this.navLink).removeClass('active');
- $currentTab.addClass('active');
- }
- },
-
- setActiveTab: function() {
- this.switchTab();
- },
-
- renderSection: function(tabSections, tabName, tabLabel) {
- var accountSectionView = new AccountSectionView({
- tabName: tabName,
- tabLabel: tabLabel,
- sections: tabSections,
- el: '#' + tabName + '-tabpanel'
- });
-
- accountSectionView.render();
- },
-
- showLoadingError: function() {
- this.$('.ui-loading-error').removeClass('is-hidden');
- },
-
- revertValue: function(event) {
- this.options.userPreferencesModel.trigger('revertValue', event);
- }
- });
-
- return AccountSettingsView;
- });
-}).call(this, define || RequireJS.define);
diff --git a/lms/static/learner_profile b/lms/static/learner_profile
deleted file mode 120000
index ca7ce1f79785..000000000000
--- a/lms/static/learner_profile
+++ /dev/null
@@ -1 +0,0 @@
-../../openedx/features/learner_profile/static/learner_profile
\ No newline at end of file
diff --git a/lms/static/lms/js/build.js b/lms/static/lms/js/build.js
index c22f366c5d2b..1d5e1a983be0 100644
--- a/lms/static/lms/js/build.js
+++ b/lms/static/lms/js/build.js
@@ -33,10 +33,8 @@
'js/discussions_management/views/discussions_dashboard_factory',
'js/header_factory',
'js/student_account/logistration_factory',
- 'js/student_account/views/account_settings_factory',
'js/student_account/views/finish_auth_factory',
'js/views/message_banner',
- 'learner_profile/js/learner_profile_factory',
'lms/js/preview/preview_factory',
'support/js/certificates_factory',
'support/js/enrollment_factory',
diff --git a/lms/static/lms/js/spec/main.js b/lms/static/lms/js/spec/main.js
index cd8668ddc443..4b6b0d9ec64a 100644
--- a/lms/static/lms/js/spec/main.js
+++ b/lms/static/lms/js/spec/main.js
@@ -761,9 +761,6 @@
'js/spec/shoppingcart/shoppingcart_spec.js',
'js/spec/staff_debug_actions_spec.js',
'js/spec/student_account/access_spec.js',
- 'js/spec/student_account/account_settings_factory_spec.js',
- 'js/spec/student_account/account_settings_fields_spec.js',
- 'js/spec/student_account/account_settings_view_spec.js',
'js/spec/student_account/emailoptin_spec.js',
'js/spec/student_account/enrollment_spec.js',
'js/spec/student_account/finish_auth_spec.js',
@@ -787,10 +784,6 @@
'js/spec/views/file_uploader_spec.js',
'js/spec/views/message_banner_spec.js',
'js/spec/views/notification_spec.js',
- 'learner_profile/js/spec/learner_profile_factory_spec.js',
- 'learner_profile/js/spec/views/learner_profile_fields_spec.js',
- 'learner_profile/js/spec/views/learner_profile_view_spec.js',
- 'learner_profile/js/spec/views/section_two_tab_spec.js',
'support/js/spec/collections/enrollment_spec.js',
'support/js/spec/models/enrollment_spec.js',
'support/js/spec/views/certificates_spec.js',
diff --git a/lms/static/sass/_build-lms-v1.scss b/lms/static/sass/_build-lms-v1.scss
index 7a77eb34ca07..90c5077c1f38 100644
--- a/lms/static/sass/_build-lms-v1.scss
+++ b/lms/static/sass/_build-lms-v1.scss
@@ -52,7 +52,6 @@
@import 'multicourse/survey-page';
// base - specific views
-@import 'views/account-settings';
@import 'views/course-entitlements';
@import 'views/login-register';
@import 'views/verification';
@@ -68,7 +67,6 @@
// features
@import 'features/bookmarks-v1';
-@import 'features/learner-profile';
@import 'features/_unsupported-browser-alert';
@import 'features/content-type-gating';
@import 'features/course-duration-limits';
diff --git a/lms/static/sass/features/_learner-profile.scss b/lms/static/sass/features/_learner-profile.scss
deleted file mode 100644
index 8d35a7eccc67..000000000000
--- a/lms/static/sass/features/_learner-profile.scss
+++ /dev/null
@@ -1,875 +0,0 @@
-// lms - application - learner profile
-// ====================
-
-.learner-achievements {
- .learner-message {
- @extend %no-content;
-
- margin: $baseline*0.75 0;
-
- .message-header,
- .message-actions {
- text-align: center;
- }
-
- .message-actions {
- margin-top: $baseline/2;
-
- .btn-brand {
- color: $white;
- }
- }
- }
-}
-
-.certificate-card {
- display: flex;
- flex-direction: row;
- margin-bottom: $baseline;
- padding: $baseline/2;
- border: 1px;
- border-style: solid;
- background-color: $white;
- cursor: pointer;
-
- &:hover {
- box-shadow: 0 0 1px 1px $gray-l2;
- }
-
- .card-logo {
- @include margin-right($baseline);
-
- width: 100px;
- height: 100px;
-
- @media (max-width: $learner-profile-container-flex) { // Switch to map-get($grid-breakpoints,md) for bootstrap
- display: none;
- }
- }
-
- .card-content {
- color: $body-color;
- margin-top: $baseline/2;
- }
-
- .card-supertitle {
- @extend %t-title6;
-
- color: $lightest-base-font-color;
- }
-
- .card-title {
- @extend %t-title5;
- @extend %t-strong;
-
- margin-bottom: $baseline/2;
- }
-
- .card-text {
- @extend %t-title8;
-
- color: $lightest-base-font-color;
- }
-
- &.mode-audit {
- border-color: $audit-mode-color;
-
- .card-logo {
- background-image: url('#{$static-path}/images/certificates/audit.png');
- }
- }
-
- &.mode-honor {
- border-color: $honor-mode-color;
-
- .card-logo {
- background-image: url('#{$static-path}/images/certificates/honor.png');
- }
- }
-
- &.mode-verified {
- border-color: $verified-mode-color;
-
- .card-logo {
- background-image: url('#{$static-path}/images/certificates/verified.png');
- }
- }
-
- &.mode-professional {
- border-color: $professional-certificate-color;
-
- .card-logo {
- background-image: url('#{$static-path}/images/certificates/professional.png');
- }
- }
-}
-
-.view-profile {
- $profile-image-dimension: 120px;
-
- .window-wrap,
- .content-wrapper {
- background-color: $body-bg;
- padding: 0;
- margin-top: 0;
- }
-
- .page-banner {
- background-color: $gray-l4;
- max-width: none;
-
- .user-messages {
- max-width: map-get($container-max-widths, xl);
- margin: auto;
- padding: $baseline/2;
- }
- }
-
- .ui-loading-indicator {
- @extend .ui-loading-base;
-
- padding-bottom: $baseline;
-
- // center horizontally
- @include margin-left(auto);
- @include margin-right(auto);
-
- width: ($baseline*5);
- }
-
- .profile-image-field {
- button {
- background: transparent !important;
- border: none !important;
- padding: 0;
- }
-
- .u-field-image {
- padding-top: 0;
- padding-bottom: ($baseline/4);
- }
-
- .image-wrapper {
- width: $profile-image-dimension;
- position: relative;
- margin: auto;
-
- .image-frame {
- display: block;
- position: relative;
- width: $profile-image-dimension;
- height: $profile-image-dimension;
- border-radius: ($profile-image-dimension/2);
- overflow: hidden;
- border: 3px solid $gray-l6;
- margin-top: $baseline*-0.75;
- background: $white;
- }
-
- .u-field-upload-button {
- position: absolute;
- top: 0;
- opacity: 0;
- width: $profile-image-dimension;
- height: $profile-image-dimension;
- border-radius: ($profile-image-dimension/2);
- border: 2px dashed transparent;
- background: rgba(229, 241, 247, 0.8);
- color: $link-color;
- text-shadow: none;
-
- @include transition(all $tmg-f1 ease-in-out 0s);
-
- z-index: 6;
-
- i {
- color: $link-color;
- }
-
- &:focus,
- &:hover {
- @include show-hover-state();
-
- border-color: $link-color;
- }
-
- &.in-progress {
- opacity: 1;
- }
- }
-
- .button-visible {
- @include show-hover-state();
- }
-
- .upload-button-icon,
- .upload-button-title {
- display: block;
- margin-bottom: ($baseline/4);
-
- @include transform(translateY(35px));
-
- line-height: 1.3em;
- text-align: center;
- z-index: 7;
- color: $body-color;
- }
-
- .upload-button-input {
- position: absolute;
- top: 0;
-
- @include left(0);
-
- width: $profile-image-dimension;
- border-radius: ($profile-image-dimension/2);
- height: 100%;
- cursor: pointer;
- z-index: 5;
- outline: 0;
- opacity: 0;
- }
-
- .u-field-remove-button {
- position: relative;
- display: block;
- width: $profile-image-dimension;
- margin-top: ($baseline / 4);
- padding: ($baseline / 5) 0 0;
- text-align: center;
- opacity: 0;
- transition: opacity 0.5s;
- }
-
- &:hover,
- &:active {
- .u-field-remove-button {
- opacity: 1;
- }
- }
- }
- }
-
- .wrapper-profile {
- min-height: 200px;
- background-color: $gray-l6;
-
- .ui-loading-indicator {
- margin-top: 100px;
- }
- }
-
- .profile-self {
- .wrapper-profile-field-account-privacy {
- @include clearfix();
-
- box-sizing: border-box;
- width: 100%;
- margin: 0 auto;
- border-bottom: 1px solid $gray-l3;
- background-color: $gray-l4;
- padding: ($baseline*0.75) 5%;
- display: table;
-
- .wrapper-profile-records {
- display: table-row;
-
- button {
- @extend %btn-secondary-blue-outline;
-
- margin-top: 1em;
- background: $blue;
- color: #fff;
- }
- }
-
- @include media-breakpoint-up(sm) {
- .wrapper-profile-records {
- display: table-cell;
- vertical-align: middle;
- white-space: nowrap;
-
- button {
- margin-top: 0;
- }
- }
- }
-
- .u-field-account_privacy {
- @extend .container;
-
- display: table-cell;
- border: none;
- box-shadow: none;
- padding: 0;
- margin: 0;
- vertical-align: middle;
-
- @media (max-width: $learner-profile-container-flex) { // Switch to map-get($grid-breakpoints,md) for bootstrap
- max-width: calc(100% - 40px);
- min-width: auto;
- }
-
- .btn-change-privacy {
- @extend %btn-primary-blue;
-
- padding-top: 4px;
- padding-bottom: 5px;
- background-image: none;
- box-shadow: none;
- }
- }
-
- .u-field-title {
- @extend %t-strong;
-
- width: auto;
- color: $body-color;
- cursor: text;
- text-shadow: none; // override bad lms styles on labels
- }
-
- .u-field-value {
- width: auto;
-
- @include margin-left($baseline/2);
- }
-
- .u-field-message {
- @include float(left);
-
- width: 100%;
- padding: 0;
- color: $body-color;
-
- .u-field-message-notification {
- color: $gray-d2;
- }
- }
- }
- }
-
- .wrapper-profile-sections {
- @extend .container;
-
- @include padding($baseline*1.5, 5%, $baseline*1.5, 5%);
-
- display: flex;
- min-width: 0;
- max-width: 100%;
-
- @media (max-width: $learner-profile-container-flex) { // Switch to map-get($grid-breakpoints,md) for bootstrap
- @include margin-left(0);
-
- flex-wrap: wrap;
- }
- }
-
- .profile-header {
- max-width: map-get($container-max-widths, xl);
- margin: auto;
- padding: $baseline 5% 0;
-
- .header {
- @extend %t-title4;
- @extend %t-ultrastrong;
-
- display: inline-block;
- color: #222;
- }
-
- .subheader {
- @extend %t-title6;
- }
- }
-
- .wrapper-profile-section-container-one {
- @media (max-width: $learner-profile-container-flex) { // Switch to map-get($grid-breakpoints,md) for bootstrap
- width: 100%;
- }
-
- .wrapper-profile-section-one {
- width: 300px;
- background-color: $white;
- border-top: 5px solid $blue;
- padding-bottom: $baseline;
-
- @media (max-width: $learner-profile-container-flex) { // Switch to map-get($grid-breakpoints,md) for bootstrap
- @include margin-left(0);
-
- width: 100%;
- }
-
- .profile-section-one-fields {
- margin: 0 $baseline/2;
-
- .social-links {
- @include padding($baseline/4, 0, 0, $baseline/4);
-
- font-size: 2rem;
-
- & > span {
- color: $gray-l4;
- }
-
- a {
- .fa-facebook-square {
- color: $facebook-blue;
- }
-
- .fa-twitter-square {
- color: $twitter-blue;
- }
-
- .fa-linkedin-square {
- color: $linkedin-blue;
- }
- }
- }
-
- .u-field {
- font-weight: $font-semibold;
-
- @include padding(0, 0, 0, 3px);
-
- color: $body-color;
- margin-top: $baseline/5;
-
- .u-field-value,
- .u-field-title {
- font-weight: 500;
- width: calc(100% - 40px);
- color: $lightest-base-font-color;
- }
-
- .u-field-value-readonly {
- font-family: $font-family-sans-serif;
- color: $darkest-base-font-color;
- }
-
- &.u-field-dropdown {
- position: relative;
-
- &:not(.editable-never) {
- cursor: pointer;
- }
- }
-
- &:not(.u-field-readonly) {
- &.u-field-value {
- @extend %t-weight3;
- }
-
- &:not(:last-child) {
- padding-bottom: $baseline/4;
- border-bottom: 1px solid $border-color;
-
- &:hover.mode-placeholder {
- padding-bottom: $baseline/5;
- border-bottom: 2px dashed $link-color;
- }
- }
- }
- }
-
- & > .u-field {
- &:not(:first-child) {
- font-size: $body-font-size;
- color: $body-color;
- font-weight: $font-light;
- margin-bottom: 0;
- }
-
- &:first-child {
- @extend %t-title4;
- @extend %t-weight4;
-
- font-size: em(24);
- }
- }
-
- select {
- width: 85%;
- }
-
- .u-field-message {
- @include right(0);
-
- position: absolute;
- top: 0;
- width: 20px;
-
- .icon {
- vertical-align: baseline;
- }
- }
- }
- }
- }
-
-
- .wrapper-profile-section-container-two {
- @include float(left);
- @include padding-left($baseline);
-
- font-family: $font-family-sans-serif;
- flex-grow: 1;
-
- @media (max-width: $learner-profile-container-flex) { // Switch to map-get($grid-breakpoints,md) for bootstrap
- width: 90%;
- margin-top: $baseline;
- padding: 0;
- }
-
- .u-field-textarea {
- @include padding(0, ($baseline*0.75), ($baseline*0.75), 0);
-
- margin-bottom: ($baseline/2);
-
- @media (max-width: $learner-profile-container-flex) { // Switch to map-get($grid-breakpoints,md) for bootstrap
- @include padding-left($baseline/4);
- }
-
- .u-field-header {
- position: relative;
-
- .u-field-message {
- @include right(0);
-
- top: $baseline/4;
- position: absolute;
- }
- }
-
- &.editable-toggle {
- cursor: pointer;
- }
- }
-
- .u-field-title {
- @extend %t-title6;
-
- display: inline-block;
- margin-top: 0;
- margin-bottom: ($baseline/4);
- color: $gray-d3;
- width: 100%;
- font: $font-semibold 1.4em/1.4em $font-family-sans-serif;
- }
-
- .u-field-value {
- @extend %t-copy-base;
-
- width: 100%;
- overflow: auto;
-
- textarea {
- width: 100%;
- background-color: transparent;
- border-radius: 5px;
- border-color: $gray-d1;
- resize: none;
- white-space: pre-line;
- outline: 0;
- box-shadow: none;
- -webkit-appearance: none;
- }
-
- a {
- color: inherit;
- }
- }
-
- .u-field-message {
- @include float(right);
-
- width: auto;
-
- .message-can-edit {
- position: absolute;
- }
- }
-
- .u-field.mode-placeholder {
- padding: $baseline;
- margin: $baseline*0.75 0;
- border: 2px dashed $gray-l3;
-
- i {
- font-size: 12px;
-
- @include padding-right(5px);
-
- vertical-align: middle;
- color: $body-color;
- }
-
- .u-field-title {
- width: 100%;
- text-align: center;
- }
-
- .u-field-value {
- text-align: center;
- line-height: 1.5em;
-
- @extend %t-copy-sub1;
-
- color: $body-color;
- }
-
- &:hover {
- border: 2px dashed $link-color;
-
- .u-field-title,
- i {
- color: $link-color;
- }
- }
- }
-
- .wrapper-u-field {
- font-size: $body-font-size;
- color: $body-color;
-
- .u-field-header .u-field-title {
- color: $body-color;
- }
-
- .u-field-footer {
- .field-textarea-character-count {
- @extend %t-weight1;
-
- @include float(right);
-
- margin-top: $baseline/4;
- }
- }
- }
-
- .profile-private-message {
- @include padding-left($baseline*0.75);
-
- line-height: 3em;
- }
- }
-
- .badge-paging-header {
- padding-top: $baseline;
- }
-
- .page-content-nav {
- @extend %page-content-nav;
- }
-
- .badge-set-display {
- @extend .container;
-
- padding: 0;
-
- .badge-list {
- // We're using a div instead of ul for accessibility, so we have to match the style
- // used by ul.
- margin: 1em 0;
- padding: 0 0 0 40px;
- }
-
- .badge-display {
- width: 50%;
- display: inline-block;
- vertical-align: top;
- padding: 2em 0;
-
- .badge-image-container {
- padding-right: $baseline;
- margin-left: 1em;
- width: 20%;
- vertical-align: top;
- display: inline-block;
-
- img.badge {
- width: 100%;
- }
-
- .accomplishment-placeholder {
- border: 4px dotted $gray-l4;
- border-radius: 50%;
- display: block;
- width: 100%;
- padding-bottom: 100%;
- }
- }
-
- .badge-details {
- @extend %t-copy-sub1;
- @extend %t-regular;
-
- max-width: 70%;
- display: inline-block;
- color: $gray-d1;
-
- .badge-name {
- @extend %t-strong;
- @extend %t-copy-base;
-
- color: $gray-d3;
- }
-
- .badge-description {
- padding-bottom: $baseline;
- line-height: 1.5em;
- }
-
- .badge-date-stamp {
- @extend %t-copy-sub1;
- }
-
- .find-button-container {
- border: 1px solid $blue-l1;
- padding: ($baseline / 2) $baseline ($baseline / 2) $baseline;
- display: inline-block;
- border-radius: 5px;
- font-weight: bold;
- color: $blue-s3;
- }
-
- .share-button {
- @extend %t-action3;
- @extend %button-reset;
-
- background: $gray-l6;
- color: $gray-d1;
- padding: ($baseline / 4) ($baseline / 2);
- margin-bottom: ($baseline / 2);
- display: inline-block;
- border-radius: 5px;
- border: 2px solid $gray-d1;
- cursor: pointer;
- transition: background 0.5s;
-
- .share-prefix {
- display: inline-block;
- vertical-align: middle;
- }
-
- .share-icon-container {
- display: inline-block;
-
- img.icon-mozillaopenbadges {
- max-width: 1.5em;
- margin-right: 0.25em;
- }
- }
-
- &:hover {
- background: $gray-l4;
- }
-
- &:active {
- box-shadow: inset 0 4px 15px 0 $black-t2;
- transition: none;
- }
- }
- }
- }
-
- .badge-placeholder {
- background-color: $gray-l7;
- box-shadow: inset 0 0 4px 0 $gray-l4;
- }
- }
-
- // ------------------------------
- // #BADGES MODAL
- // ------------------------------
- .badges-overlay {
- @extend %ui-depth1;
-
- position: fixed;
- top: 0;
- left: 0;
- background-color: $dark-trans-bg; /* dim the background */
- width: 100%;
- height: 100%;
- vertical-align: middle;
-
- .badges-modal {
- @extend %t-copy-lead1;
- @extend %ui-depth2;
-
- color: $lighter-base-font-color;
- box-sizing: content-box;
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 80%;
- max-width: 700px;
- max-height: calc(100% - 100px);
- margin-right: auto;
- margin-left: auto;
- border-top: rem(10) solid $blue-l2;
- background: $light-gray3;
- padding-right: ($baseline * 2);
- padding-left: ($baseline * 2);
- padding-bottom: ($baseline);
- overflow-x: hidden;
-
- .modal-header {
- margin-top: ($baseline / 2);
- margin-bottom: ($baseline / 2);
- }
-
- .close {
- @extend %button-reset;
- @extend %t-strong;
-
- color: $lighter-base-font-color;
- position: absolute;
- right: ($baseline);
- top: $baseline;
- cursor: pointer;
- padding: ($baseline / 4) ($baseline / 2);
-
- @include transition(all $tmg-f2 ease-in-out 0s);
-
- &:focus,
- &:hover {
- background-color: $blue-d2;
- border-radius: 3px;
- color: $white;
- }
- }
-
- .badges-steps {
- display: table;
- }
-
- .image-container {
- // Lines the image up with the content of the above list.
- @include ltr {
- @include padding-left(2em);
- }
-
- @include rtl {
- @include padding-right(1em);
-
- float: right;
- }
- }
-
- .backpack-logo {
- @include float(right);
- @include margin-left($baseline);
- }
- }
- }
-
- .modal-hr {
- display: block;
- border: none;
- background-color: $light-gray;
- height: rem(2);
- width: 100%;
- }
-}
diff --git a/lms/static/sass/partials/lms/theme/_variables-v1.scss b/lms/static/sass/partials/lms/theme/_variables-v1.scss
index 1cff0168aced..5dca9b849534 100644
--- a/lms/static/sass/partials/lms/theme/_variables-v1.scss
+++ b/lms/static/sass/partials/lms/theme/_variables-v1.scss
@@ -527,9 +527,6 @@ $palette-success-border: #b9edb9;
$palette-success-back: #ecfaec;
$palette-success-text: #008100;
-// learner profile elements
-$learner-profile-container-flex: 768px;
-
// course elements
$course-bg-color: $uxpl-grayscale-x-back !default;
$account-content-wrapper-bg: shade($body-bg, 2%) !default;
diff --git a/lms/static/sass/views/_account-settings.scss b/lms/static/sass/views/_account-settings.scss
deleted file mode 100644
index a4e5ff76eab6..000000000000
--- a/lms/static/sass/views/_account-settings.scss
+++ /dev/null
@@ -1,683 +0,0 @@
-// lms - application - account settings
-// ====================
-
-// Table of Contents
-// * +Container - Account Settings
-// * +Main - Header
-// * +Settings Section
-// * +Alert Messages
-
-
-// +Container - Account Settings
-.wrapper-account-settings {
- background: $white;
- width: 100%;
-
- .account-settings-container {
- max-width: grid-width(12);
- padding: 10px;
- margin: 0 auto;
- }
-
- .ui-loading-indicator,
- .ui-loading-error {
- @extend .ui-loading-base;
- // center horizontally
- @include margin-left(auto);
- @include margin-right(auto);
-
- padding: ($baseline*3);
- text-align: center;
-
- .message-error {
- color: $alert-color;
- }
- }
-}
-
-// +Main - Header
-.wrapper-account-settings {
- .wrapper-header {
- max-width: grid-width(12);
- height: 139px;
- border-bottom: 4px solid $m-gray-l4;
-
- .header-title {
- @extend %t-title4;
-
- margin-bottom: ($baseline/2);
- padding-top: ($baseline*2);
- }
-
- .header-subtitle {
- color: $gray-l2;
- }
-
- .account-nav {
- @include float(left);
-
- margin: ($baseline/2) 0;
- padding: 0;
- list-style: none;
-
- .account-nav-link {
- @include float(left);
-
- font-size: em(14);
- color: $gray;
- padding: $baseline/4 $baseline*1.25 $baseline;
- display: inline-block;
- box-shadow: none;
- border-bottom: 4px solid transparent;
- border-radius: 0;
- background: transparent none;
- }
-
- button {
- @extend %ui-clear-button;
- @extend %btn-no-style;
-
- @include appearance(none);
-
- display: block;
- padding: ($baseline/4);
-
- &:hover,
- &:focus {
- text-decoration: none;
- border-bottom-color: $courseware-border-bottom-color;
- }
-
- &.active {
- border-bottom-color: theme-color("dark");
- }
- }
- }
-
- @include media-breakpoint-down(md) {
- border-bottom-color: transparent;
-
- .account-nav {
- display: flex;
- border-bottom: none;
-
- .account-nav-link {
- border-bottom: 4px solid theme-color("light");
- }
- }
- }
- }
-}
-
-// +Settings Section
-.account-settings-sections {
- .section-header {
- @extend %t-title5;
- @extend %t-strong;
-
- padding-top: ($baseline/2)*3;
- color: $dark-gray1;
- }
-
- .section {
- background-color: $white;
- margin: $baseline 5% 0;
- border-bottom: 4px solid $m-gray-l4;
-
- .account-settings-header-subtitle {
- font-size: em(14);
- line-height: normal;
- color: $dark-gray;
- padding-bottom: 10px;
- }
-
- .account-settings-header-subtitle-warning {
- @extend .account-settings-header-subtitle;
-
- color: $alert-color;
- }
-
- .account-settings-section-body {
- .u-field {
- border-bottom: 2px solid $m-gray-l4;
- padding: $baseline*0.75 0;
-
- .field {
- width: 30%;
- vertical-align: top;
- display: inline-block;
- position: relative;
-
- select {
- @include appearance(none);
-
- padding: 14px 30px 14px 15px;
- border: 1px solid $gray58-border;
- background-color: transparent;
- border-radius: 2px;
- position: relative;
- z-index: 10;
-
- &::-ms-expand {
- display: none;
- }
-
- ~ .icon-caret-down {
- &::after {
- content: "";
- border-left: 6px solid transparent;
- border-right: 6px solid transparent;
- border-top: 7px solid $blue;
- position: absolute;
- right: 10px;
- bottom: 20px;
- z-index: 0;
- }
- }
- }
-
- .field-label {
- display: block;
- width: auto;
- margin-bottom: 0.625rem;
- font-size: 1rem;
- line-height: 1;
- color: $dark-gray;
- white-space: nowrap;
- }
-
- .field-input {
- @include transition(all 0.125s ease-in-out 0s);
-
- display: inline-block;
- padding: 0.625rem;
- border: 1px solid $gray58-border;
- border-radius: 2px;
- background: $white;
- font-size: $body-font-size;
- color: $dark-gray;
- width: 100%;
- height: 48px;
- box-shadow: none;
- }
-
- .u-field-link {
- @extend %ui-clear-button;
-
- // set styles
- @extend %btn-pl-default-base;
-
- @include font-size(18);
-
- width: 100%;
- border: 1px solid $blue;
- color: $blue;
- padding: 11px 14px;
- line-height: normal;
- }
- }
-
- .u-field-order {
- display: flex;
- align-items: center;
- font-size: em(16);
- font-weight: 600;
- color: $dark-gray;
- width: 100%;
- padding-top: $baseline;
- padding-bottom: $baseline;
- line-height: normal;
- flex-flow: row wrap;
-
- span {
- padding: $baseline;
- }
-
- .u-field-order-number {
- @include float(left);
-
- width: 30%;
- }
-
- .u-field-order-date {
- @include float(left);
-
- padding-left: 30px;
- width: 20%;
- }
-
- .u-field-order-price {
- @include float(left);
-
- width: 15%;
- }
-
- .u-field-order-link {
- width: 10%;
- padding: 0;
-
- .u-field-link {
- @extend %ui-clear-button;
- @extend %btn-pl-default-base;
-
- @include font-size(14);
-
- border: 1px solid $blue;
- color: $blue;
- line-height: normal;
- padding: 10px;
- width: 110px;
- }
- }
- }
-
- .u-field-order-lines {
- @extend .u-field-order;
-
- padding: 5px 0 0;
- font-weight: 100;
-
- .u-field-order-number {
- padding: 20px 10px 20px 30px;
- }
- }
-
- .social-field-linked {
- background: $m-gray-l4;
- box-shadow: 0 1px 2px 1px $shadow-l2;
- padding: 1.25rem;
- box-sizing: border-box;
- margin: 10px;
- width: 100%;
-
- .field-label {
- @include font-size(24);
- }
-
- .u-field-social-help {
- display: inline-block;
- padding: 20px 0 6px;
- }
-
- .u-field-link {
- @include font-size(14);
- @include text-align(left);
-
- border: none;
- margin-top: $baseline;
- font-weight: $font-semibold;
- padding: 0;
-
- &:focus,
- &:hover,
- &:active {
- background-color: transparent;
- color: $m-blue-d3;
- border: none;
- }
- }
- }
-
- .social-field-unlinked {
- background: $m-gray-l4;
- box-shadow: 0 1px 2px 1px $shadow-l2;
- padding: 1.25rem;
- box-sizing: border-box;
- text-align: center;
- margin: 10px;
- width: 100%;
-
- .field-label {
- @include font-size(24);
-
- text-align: center;
- }
-
- .u-field-link {
- @include font-size(14);
-
- margin-top: $baseline;
- font-weight: $font-semibold;
- }
- }
-
- .u-field-message {
- position: relative;
- padding: $baseline*0.75 0 0 ($baseline*4);
- width: 60%;
-
- .u-field-message-notification {
- position: absolute;
- left: 0;
- top: 0;
- bottom: 0;
- margin: auto;
- padding: 38px 0 0 ($baseline*5);
- }
- }
-
- &:last-child {
- border-bottom: none;
- margin-bottom: ($baseline*2);
- }
-
- // Responsive behavior
- @include media-breakpoint-down(md) {
- .u-field-value {
- width: 100%;
- }
-
- .u-field-message {
- width: 100%;
- padding: $baseline/2 0;
-
- .u-field-message-notification {
- position: relative;
- padding: 0;
- }
- }
-
- .u-field-order {
- display: flex;
- flex-wrap: nowrap;
-
- .u-field-order-number,
- .u-field-order-date,
- .u-field-order-price,
- .u-field-order-link {
- width: auto;
- float: none;
- flex-grow: 1;
-
- &:first-of-type {
- flex-grow: 2;
- }
- }
- }
- }
- }
-
- .u-field {
- &.u-field-dropdown,
- &.editable-never &.mode-display {
- .u-field-value {
- margin-bottom: ($baseline);
-
- .u-field-title {
- font-size: 16px;
- line-height: 22px;
- margin-bottom: 18px;
- }
-
- .u-field-value-readonly {
- font-size: 22px;
- color: #636c72;
- line-height: 30px;
- white-space: nowrap;
- }
- }
- }
- }
-
- .u-field-readonly .u-field-title {
- font-size: 16px;
- color: #636c72;
- line-height: 22px;
- padding-top: ($baseline/2);
- padding-bottom: 0;
- margin-bottom: 8px !important;
- }
-
- .u-field-readonly .u-field-value {
- font-size: 22px;
- color: #636c72;
- line-height: 30px;
- padding-top: 8px;
- padding-bottom: ($baseline);
- white-space: nowrap;
- }
-
- .u-field-orderHistory {
- border-bottom: none;
- border: 1px solid $m-gray-l4;
- margin-bottom: $baseline;
- padding: 0;
-
- &:last-child {
- border-bottom: 1px solid $m-gray-l4;
- }
-
- &:hover,
- &:focus {
- background-color: $light-gray4;
- }
- }
-
- .u-field-order-orderId {
- border: none;
- margin-top: $baseline;
- margin-bottom: 0;
- padding-bottom: 0;
-
- &:hover,
- &:focus {
- background-color: transparent;
- }
-
- .u-field-order {
- font-weight: $font-semibold;
- padding-top: 0;
- padding-bottom: 0;
-
- .u-field-order-title {
- font-size: em(16);
- }
- }
- }
-
- .u-field-social {
- border-bottom: none;
- margin-right: 20px;
- width: 30%;
- display: inline-block;
- vertical-align: top;
-
- .u-field-social-help {
- @include font-size(12);
-
- color: $m-gray-d1;
- }
- }
- }
-
- .account-deletion-details {
- .btn-outline-primary {
- @extend %ui-clear-button;
-
- // set styles
- @extend %btn-pl-default-base;
-
- @include font-size(18);
-
- border: 1px solid $blue;
- color: $blue;
- padding: 11px 14px;
- line-height: normal;
- margin: 20px 0;
- }
-
- .paragon__modal-open {
- overflow-y: scroll;
- color: $dark-gray;
-
- .paragon__modal-title {
- font-weight: $font-semibold;
- }
-
- .paragon__modal-body {
- line-height: 1.5;
-
- .alert-title {
- line-height: 1.5;
- }
- }
-
- .paragon__alert-warning {
- color: $dark-gray;
- }
-
- .next-steps {
- margin-bottom: 10px;
- font-weight: $font-semibold;
- }
-
- .confirm-password-input {
- width: 50%;
- }
-
- .paragon__btn:not(.cancel-btn) {
- @extend %btn-primary-blue;
- }
- }
-
- .modal-alert {
- display: flex;
-
- .icon-wrapper {
- padding-right: 15px;
- }
-
- .alert-content {
- .alert-title {
- color: $dark-gray;
- margin-bottom: 10px;
- font: {
- size: 1rem;
- weight: $font-semibold;
- }
- }
-
- a {
- color: $blue-u1;
- }
- }
- }
-
- .delete-confirmation-wrapper {
- .paragon__modal-footer {
- .paragon__btn-outline-primary {
- @extend %ui-clear-button;
-
- // set styles
- @extend %btn-pl-default-base;
-
- @include margin-left(25px);
-
- border-color: $blue;
- color: $blue;
- padding: 11px 14px;
- line-height: normal;
- }
- }
- }
- }
-
- &:last-child {
- border-bottom: none;
- }
- }
-}
-
-// * +Alert Messages
-.account-settings-message,
-.account-settings-section-message {
- font-size: 16px;
- line-height: 22px;
- margin-top: 15px;
- margin-bottom: 30px;
-
- .alert-message {
- color: #292b2c;
- font-family: $font-family-sans-serif;
- position: relative;
- padding: 10px 10px 10px 35px;
- border: 1px solid transparent;
- border-radius: 0;
- box-shadow: none;
- margin-bottom: 8px;
-
- & > .fa {
- position: absolute;
- left: 11px;
- top: 13px;
- font-size: 16px;
- }
-
- span {
- display: block;
-
- a {
- text-decoration: underline;
- }
- }
- }
-
- .success {
- background-color: #ecfaec;
- border-color: #b9edb9;
- }
-
- .info {
- background-color: #d8edf8;
- border-color: #bbdff2;
- }
-
- .warning {
- background-color: #fcf8e3;
- border-color: #faebcc;
- }
-
- .error {
- background-color: #f2dede;
- border-color: #ebccd1;
- }
-}
-
-.account-settings-message {
- margin-bottom: 0;
-
- .alert-message {
- padding: 10px;
-
- .alert-actions {
- margin-top: 10px;
-
- .btn-alert-primary {
- @extend %btn-primary-blue;
-
- @include font-size(18);
-
- border: 1px solid $m-blue-d3;
- border-radius: 3px;
- box-shadow: none;
- padding: 11px 14px;
- line-height: normal;
- }
-
- .btn-alert-secondary {
- @extend %ui-clear-button;
-
- // set styles
- @extend %btn-pl-default-base;
-
- @include font-size(18);
-
- background-color: white;
- border: 1px solid $blue;
- color: $blue;
- padding: 11px 14px;
- line-height: normal;
- }
- }
- }
-}
diff --git a/lms/templates/dashboard/_dashboard_third_party_error.html b/lms/templates/dashboard/_dashboard_third_party_error.html
deleted file mode 100644
index 5b9efe0bbddd..000000000000
--- a/lms/templates/dashboard/_dashboard_third_party_error.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<%page expression_filter="h"/>
-
-<%! from django.utils.translation import gettext as _ %>
-
-
-
-
${_("Could Not Link Accounts")}
-
- ## Translators: this message is displayed when a user tries to link their account with a third-party authentication provider (for example, Google or LinkedIn) with a given edX account, but their third-party account is already associated with another edX account. provider_name is the name of the third-party authentication provider, and platform_name is the name of the edX deployment.
-
${_("The {provider_name} account you selected is already linked to another {platform_name} account.").format(provider_name=duplicate_provider, platform_name=platform_name)}
-
-
-
-
diff --git a/lms/templates/header/user_dropdown.html b/lms/templates/header/user_dropdown.html
index 2e7e168a6937..b4b22e0e32be 100644
--- a/lms/templates/header/user_dropdown.html
+++ b/lms/templates/header/user_dropdown.html
@@ -4,13 +4,13 @@
<%!
import json
+from urllib.parse import urljoin
from django.conf import settings
from django.urls import reverse
from django.utils.translation import gettext as _
from openedx.core.djangoapps.user_api.accounts.image_helpers import get_profile_image_urls_for_user
-from openedx.core.djangoapps.user_api.accounts.toggles import should_redirect_to_order_history_microfrontend
from openedx.features.enterprise_support.utils import get_enterprise_learner_generic_name, get_enterprise_learner_portal
%>
@@ -23,7 +23,7 @@
enterprise_customer_portal = get_enterprise_learner_portal(request)
## Enterprises with the learner portal enabled should not show order history, as it does
## not apply to the learner's method of purchasing content.
-should_show_order_history = should_redirect_to_order_history_microfrontend() and not enterprise_customer_portal
+should_show_order_history = not enterprise_customer_portal
%>
-
- ## Translators: this section lists all the third-party authentication providers (for example, Google and LinkedIn) the user can link with or unlink from their edX account.
- ${_("Connected Accounts")}
-
-
-
- % for state in provider_user_states:
-