diff --git a/cms/envs/test.py b/cms/envs/test.py index e7be26b73c0e..4237d884e82c 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, BLOCKSTORE_USE_BLOCKSTORE_APP_API, BLOCKSTORE_API_URL, COMPREHENSIVE_THEME_DIRS, # unimport:skip @@ -37,8 +38,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, 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 1230f8320a75..8f35afdf4299 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.should_redirect_to_learner_home_mfe') def test_redirect_to_learner_home(self, mock_should_redirect_to_learner_home_mfe): diff --git a/common/djangoapps/student/views/dashboard.py b/common/djangoapps/student/views/dashboard.py index 0e73c3c25700..693e64ee933d 100644 --- a/common/djangoapps/student/views/dashboard.py +++ b/common/djangoapps/student/views/dashboard.py @@ -519,7 +519,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 should_redirect_to_learner_home_mfe(user): return redirect(settings.LEARNER_HOME_MICROFRONTEND_URL) @@ -624,7 +624,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("") ) @@ -897,7 +897,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 aea4c18367e6..99e9e2744d58 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..0783c5557324 100644 --- a/common/djangoapps/third_party_auth/tests/specs/base.py +++ b/common/djangoapps/third_party_auth/tests/specs/base.py @@ -11,7 +11,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 +28,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 @@ -99,6 +98,43 @@ 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 _get_user_providers_state(self, request): + """ + Return provider user states and duplicated providers. + """ + 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 data['duplicate_provider'] to contain + the duplicate provider backend name. If linked is passed, we conditionally + 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 data['duplicate_provider'] == self.provider.backend_name + else: + assert data['duplicate_provider'] is None + + if linked is not None: + expected_provider = [ + 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 + 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. @@ -118,27 +154,6 @@ def assert_register_form_populates_unicode_username_correctly(self, request): # 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. - - If duplicate is True, we expect context['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 - its connected state is correct. - """ - if duplicate: - assert context['duplicate_provider'] == self.provider.backend_name - else: - assert context['duplicate_provider'] is None - - if linked is not None: - expected_provider = [ - provider for provider in context['auth']['providers'] if provider['name'] == self.provider.name - ][0] - assert expected_provider is not None - assert expected_provider['connected'] == linked - def assert_exception_redirect_looks_correct(self, expected_uri, auth_entry=None): """Tests middleware conditional redirection. @@ -611,7 +626,7 @@ def test_full_pipeline_succeeds_for_linking_account(self, _mock_segment_track): # 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,7 +645,7 @@ 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, @@ -662,7 +677,7 @@ def test_full_pipeline_succeeds_for_unlinking_account(self): 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. @@ -676,7 +691,7 @@ def test_full_pipeline_succeeds_for_unlinking_account(self): ) # 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): @@ -734,8 +749,8 @@ def test_already_associated_exception_populates_dashboard_with_error(self): 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') def test_full_pipeline_succeeds_for_signing_in_to_existing_active_account(self, _mock_segment_track): @@ -795,7 +810,7 @@ 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( @@ -937,7 +952,7 @@ def test_full_pipeline_succeeds_registering_new_account(self): ) # Now the user has been redirected to the dashboard. 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() 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..8d0bd5a5ae8d 100644 --- a/common/djangoapps/third_party_auth/tests/specs/test_testshib.py +++ b/common/djangoapps/third_party_auth/tests/specs/test_testshib.py @@ -27,7 +27,6 @@ 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 @@ -239,12 +238,10 @@ class TestShibIntegrationTest(SamlIntegrationTestUtilities, IntegrationTestMixin } @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') 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, ): @@ -284,7 +281,6 @@ def test_full_pipeline_succeeds_for_unlinking_testshib_account( } 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( @@ -299,7 +295,7 @@ def test_full_pipeline_succeeds_for_unlinking_testshib_account( request=request) # 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_third_party_accounts_state(request, linked=True) self.assert_social_auth_exists_for_user(request.user, strategy) FEATURES_WITH_ENTERPRISE_ENABLED = settings.FEATURES.copy() @@ -327,7 +323,7 @@ def test_full_pipeline_succeeds_for_unlinking_testshib_account( ) ) # 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 diff --git a/conf/locale/config.yaml b/conf/locale/config.yaml index 23cb9e0d8537..fc2fd4584800 100644 --- a/conf/locale/config.yaml +++ b/conf/locale/config.yaml @@ -157,10 +157,6 @@ segment: djangojs-partial.po: djangojs-studio.po: - cms/* - djangojs-account-settings-view.po: - - lms/static/js/student_account/views/account_settings_view.js - # Segregating student account settings view strings, so that beta language message - # can be translated for wide set of partially supported languages. mako.po: mako-studio.po: - cms/* diff --git a/lms/djangoapps/bulk_email/tasks.py b/lms/djangoapps/bulk_email/tasks.py index afad888fe0c5..a489750ec9cb 100644 --- a/lms/djangoapps/bulk_email/tasks.py +++ b/lms/djangoapps/bulk_email/tasks.py @@ -110,7 +110,7 @@ def _get_course_email_context(course): 'course_url': course_url, 'course_image_url': image_url, 'course_end_date': course_end_date, - 'account_settings_url': '{}{}'.format(lms_root_url, reverse('account_settings')), + 'account_settings_url': settings.ACCOUNT_MICROFRONTEND_URL, 'email_settings_url': '{}{}'.format(lms_root_url, reverse('dashboard')), 'logo_url': get_logo_url_for_email(), 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), diff --git a/lms/djangoapps/bulk_email/tests/test_email.py b/lms/djangoapps/bulk_email/tests/test_email.py index ba95bd26ddda..f68fd9b2499f 100644 --- a/lms/djangoapps/bulk_email/tests/test_email.py +++ b/lms/djangoapps/bulk_email/tests/test_email.py @@ -743,7 +743,7 @@ def verify_email_context(self, email_context, scheme): assert email_context['course_image_url'] == \ f'{scheme}://edx.org/asset-v1:{course_id_fragment}+type@asset+block@images_course_image.jpg' assert email_context['email_settings_url'] == f'{scheme}://edx.org/dashboard' - assert email_context['account_settings_url'] == f'{scheme}://edx.org/account/settings' + assert email_context['account_settings_url'] == settings.ACCOUNT_MICROFRONTEND_URL @override_settings(LMS_ROOT_URL="http://edx.org") def test_insecure_email_context(self): diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index fdc90a46a513..e39294517f36 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -2143,7 +2143,7 @@ def financial_assistance_form(request, course_id=None): 'header_text': _get_fa_header(FINANCIAL_ASSISTANCE_HEADER), 'student_faq_url': marketing_link('FAQ'), 'dashboard_url': reverse('dashboard'), - 'account_settings_url': reverse('account_settings'), + 'account_settings_url': settings.ACCOUNT_MICROFRONTEND_URL, 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'user_details': { 'email': user.email, diff --git a/lms/djangoapps/discussion/views.py b/lms/djangoapps/discussion/views.py index bfa511a575bd..bfb594a24b6c 100644 --- a/lms/djangoapps/discussion/views.py +++ b/lms/djangoapps/discussion/views.py @@ -4,6 +4,7 @@ import logging from functools import wraps +from urllib.parse import urljoin from django.conf import settings from django.contrib.auth import get_user_model @@ -629,7 +630,7 @@ def create_user_profile_context(request, course_key, user_id): 'page': query_params['page'], 'num_pages': query_params['num_pages'], 'sort_preference': user.default_sort_key, - 'learner_profile_page_url': reverse('learner_profile', kwargs={'username': django_user.username}), + 'learner_profile_page_url': urljoin(settings.PROFILE_MICROFRONTEND_URL, f'/u/{django_user.username}'), }) return context diff --git a/lms/envs/common.py b/lms/envs/common.py index 0bad10728565..57e586c63142 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -3249,7 +3249,6 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring 'openedx.features.course_bookmarks', 'openedx.features.course_experience', 'openedx.features.enterprise_support.apps.EnterpriseSupportConfig', - 'openedx.features.learner_profile', 'openedx.features.course_duration_limits', 'openedx.features.content_type_gating', 'openedx.features.discounts', diff --git a/lms/envs/devstack.py b/lms/envs/devstack.py index 4eae239aabbe..1ac54b95a2f6 100644 --- a/lms/envs/devstack.py +++ b/lms/envs/devstack.py @@ -385,6 +385,7 @@ def should_show_debug_toolbar(request): # lint-amnesty, pylint: disable=missing ############## Settings for Microfrontends ######################### LEARNING_MICROFRONTEND_URL = 'http://localhost:2000' ACCOUNT_MICROFRONTEND_URL = 'http://localhost:1997' +PROFILE_MICROFRONTEND_URL = 'http://localhost:1995' COMMUNICATIONS_MICROFRONTEND_URL = 'http://localhost:1984' AUTHN_MICROFRONTEND_URL = 'http://localhost:1999' AUTHN_MICROFRONTEND_DOMAIN = 'localhost:1999' diff --git a/lms/envs/test.py b/lms/envs/test.py index 7d50c943a3d0..2ae4b876a0c2 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -596,7 +596,7 @@ PDF_RECEIPT_TERMS_AND_CONDITIONS = 'add your own terms and conditions' PDF_RECEIPT_TAX_ID_LABEL = 'Tax ID' -PROFILE_MICROFRONTEND_URL = "http://profile-mfe/abc/" +PROFILE_MICROFRONTEND_URL = "http://profile-mfe" ORDER_HISTORY_MICROFRONTEND_URL = "http://order-history-mfe/" ACCOUNT_MICROFRONTEND_URL = "http://account-mfe" AUTHN_MICROFRONTEND_URL = "http://authn-mfe" diff --git a/lms/static/js/spec/student_account/account_settings_factory_spec.js b/lms/static/js/spec/student_account/account_settings_factory_spec.js deleted file mode 100644 index 075142c84ac5..000000000000 --- a/lms/static/js/spec/student_account/account_settings_factory_spec.js +++ /dev/null @@ -1,334 +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', - 'js/spec/student_account/helpers', - 'js/spec/student_account/account_settings_fields_helpers', - 'js/student_account/views/account_settings_factory', - 'js/student_account/views/account_settings_view' -], -function(Backbone, $, _, AjaxHelpers, TemplateHelpers, FieldViewsSpecHelpers, Helpers, - AccountSettingsFieldViewSpecHelpers, AccountSettingsPage) { - 'use strict'; - - describe('edx.user.AccountSettingsFactory', function() { - var createAccountSettingsPage = 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 - ); - return context.accountSettingsView; - }; - - var requests; - - beforeEach(function() { - setFixtures('
'); - }); - - 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 795782948f0b..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,14 +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/badge_list_container_spec.js', - 'learner_profile/js/spec/views/badge_list_view_spec.js', - 'learner_profile/js/spec/views/badge_view_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', - 'learner_profile/js/spec/views/share_modal_view_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 1171e3d14cdf..4d64e6768515 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'; @@ -69,7 +68,6 @@ // features @import 'features/bookmarks-v1'; @import "features/announcements"; -@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..5cb2b5f5b840 100644 --- a/lms/templates/header/user_dropdown.html +++ b/lms/templates/header/user_dropdown.html @@ -4,13 +4,14 @@ <%! 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.core.djangoapps.user_api.accounts.utils import retrieve_last_sitewide_block_completed from openedx.features.enterprise_support.utils import get_enterprise_learner_generic_name, get_enterprise_learner_portal %> @@ -23,7 +24,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 %> @@ -36,7 +36,7 @@ % else: diff --git a/lms/urls.py b/lms/urls.py index 8b314f9f1ea2..a82d134afe7a 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -669,12 +669,6 @@ include('openedx.features.calendar_sync.urls'), ), - # Learner profile - path( - 'u/', - include('openedx.features.learner_profile.urls'), - ), - # Survey Report re_path( fr'^survey_report/', diff --git a/openedx/core/djangoapps/theming/tests/test_theme_style_overrides.py b/openedx/core/djangoapps/theming/tests/test_theme_style_overrides.py index d7578f25eb87..41f91c7d1eb5 100644 --- a/openedx/core/djangoapps/theming/tests/test_theme_style_overrides.py +++ b/openedx/core/djangoapps/theming/tests/test_theme_style_overrides.py @@ -44,21 +44,6 @@ def test_footer(self): # This string comes from header.html of test-theme self.assertContains(resp, "This is a footer for test-theme.") - @with_comprehensive_theme("edx.org") - def test_account_settings_hide_nav(self): - """ - Test that theme header doesn't show marketing site links for Account Settings page. - """ - self._login() - - account_settings_url = reverse('account_settings') - response = self.client.get(account_settings_url) - - # Verify that the header navigation links are hidden for the edx.org version - self.assertNotContains(response, "How it Works") - self.assertNotContains(response, "Find courses") - self.assertNotContains(response, "Schools & Partners") - @with_comprehensive_theme("test-theme") def test_logo_image(self): """ diff --git a/openedx/core/djangoapps/user_api/accounts/settings_views.py b/openedx/core/djangoapps/user_api/accounts/settings_views.py deleted file mode 100644 index 002695d4e33f..000000000000 --- a/openedx/core/djangoapps/user_api/accounts/settings_views.py +++ /dev/null @@ -1,299 +0,0 @@ -""" Views related to Account Settings. """ - - -import logging -import urllib -from datetime import datetime - -from django.conf import settings -from django.contrib import messages -from django.contrib.auth.decorators import login_required -from django.http import HttpResponseRedirect -from django.shortcuts import redirect -from django.urls import reverse -from django.utils.translation import gettext as _ -from django.views.decorators.http import require_http_methods -from django_countries import countries - -from openedx_filters.learning.filters import AccountSettingsRenderStarted -from common.djangoapps import third_party_auth -from common.djangoapps.edxmako.shortcuts import render_to_response -from common.djangoapps.student.models import UserProfile -from common.djangoapps.third_party_auth import pipeline -from common.djangoapps.util.date_utils import strftime_localized -from lms.djangoapps.commerce.models import CommerceConfiguration -from lms.djangoapps.commerce.utils import EcommerceService -from openedx.core.djangoapps.commerce.utils import get_ecommerce_api_base_url, get_ecommerce_api_client -from openedx.core.djangoapps.dark_lang.models import DarkLangConfig -from openedx.core.djangoapps.lang_pref.api import all_languages, released_languages -from openedx.core.djangoapps.programs.models import ProgramsApiConfig -from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers -from openedx.core.djangoapps.user_api.accounts.toggles import ( - should_redirect_to_account_microfrontend, - should_redirect_to_order_history_microfrontend -) -from openedx.core.djangoapps.user_api.preferences.api import get_user_preferences -from openedx.core.lib.edx_api_utils import get_api_data -from openedx.core.lib.time_zone_utils import TIME_ZONE_CHOICES -from openedx.features.enterprise_support.api import enterprise_customer_for_request -from openedx.features.enterprise_support.utils import update_account_settings_context_for_enterprise - -log = logging.getLogger(__name__) - - -@login_required -@require_http_methods(['GET']) -def account_settings(request): - """Render the current user's account settings page. - - Args: - request (HttpRequest) - - Returns: - HttpResponse: 200 if the page was sent successfully - HttpResponse: 302 if not logged in (redirect to login page) - HttpResponse: 405 if using an unsupported HTTP method - - Example usage: - - GET /account/settings - - """ - if should_redirect_to_account_microfrontend(): - url = settings.ACCOUNT_MICROFRONTEND_URL - - duplicate_provider = pipeline.get_duplicate_provider(messages.get_messages(request)) - if duplicate_provider: - url = '{url}?{params}'.format( - url=url, - params=urllib.parse.urlencode({ - 'duplicate_provider': duplicate_provider, - }), - ) - - return redirect(url) - - context = account_settings_context(request) - - account_settings_template = 'student_account/account_settings.html' - - try: - # .. filter_implemented_name: AccountSettingsRenderStarted - # .. filter_type: org.openedx.learning.student.settings.render.started.v1 - context, account_settings_template = AccountSettingsRenderStarted.run_filter( - context=context, template_name=account_settings_template, - ) - except AccountSettingsRenderStarted.RenderInvalidAccountSettings as exc: - response = render_to_response(exc.account_settings_template, exc.template_context) - except AccountSettingsRenderStarted.RedirectToPage as exc: - response = HttpResponseRedirect(exc.redirect_to or reverse('dashboard')) - except AccountSettingsRenderStarted.RenderCustomResponse as exc: - response = exc.response - else: - response = render_to_response(account_settings_template, context) - - return response - - -def account_settings_context(request): - """ Context for the account settings page. - - Args: - request: The request object. - - Returns: - dict - - """ - user = request.user - - year_of_birth_options = [(str(year), str(year)) for year in UserProfile.VALID_YEARS] - try: - user_orders = get_user_orders(user) - except: # pylint: disable=bare-except - log.exception('Error fetching order history from Otto.') - # Return empty order list as account settings page expect a list and - # it will be broken if exception raised - user_orders = [] - - beta_language = {} - dark_lang_config = DarkLangConfig.current() - if dark_lang_config.enable_beta_languages: - user_preferences = get_user_preferences(user) - pref_language = user_preferences.get('pref-lang') - if pref_language in dark_lang_config.beta_languages_list: - beta_language['code'] = pref_language - beta_language['name'] = settings.LANGUAGE_DICT.get(pref_language) - - context = { - 'auth': {}, - 'duplicate_provider': None, - 'nav_hidden': True, - 'fields': { - 'country': { - 'options': list(countries), - }, 'gender': { - 'options': [(choice[0], _(choice[1])) for choice in UserProfile.GENDER_CHOICES], # lint-amnesty, pylint: disable=translation-of-non-string - }, 'language': { - 'options': released_languages(), - }, 'level_of_education': { - 'options': [(choice[0], _(choice[1])) for choice in UserProfile.LEVEL_OF_EDUCATION_CHOICES], # lint-amnesty, pylint: disable=translation-of-non-string - }, 'password': { - 'url': reverse('password_reset'), - }, 'year_of_birth': { - 'options': year_of_birth_options, - }, 'preferred_language': { - 'options': all_languages(), - }, 'time_zone': { - 'options': TIME_ZONE_CHOICES, - } - }, - 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), - 'password_reset_support_link': configuration_helpers.get_value( - 'PASSWORD_RESET_SUPPORT_LINK', settings.PASSWORD_RESET_SUPPORT_LINK - ) or settings.SUPPORT_SITE_LINK, - 'user_accounts_api_url': reverse("accounts_api", kwargs={'username': user.username}), - 'user_preferences_api_url': reverse('preferences_api', kwargs={'username': user.username}), - 'disable_courseware_js': True, - 'show_program_listing': ProgramsApiConfig.is_enabled(), - 'show_dashboard_tabs': True, - 'order_history': user_orders, - 'disable_order_history_tab': should_redirect_to_order_history_microfrontend(), - 'enable_account_deletion': configuration_helpers.get_value( - 'ENABLE_ACCOUNT_DELETION', settings.FEATURES.get('ENABLE_ACCOUNT_DELETION', False) - ), - 'extended_profile_fields': _get_extended_profile_fields(), - 'beta_language': beta_language, - 'enable_coppa_compliance': settings.ENABLE_COPPA_COMPLIANCE, - } - - enterprise_customer = enterprise_customer_for_request(request) - update_account_settings_context_for_enterprise(context, enterprise_customer, user) - - if third_party_auth.is_enabled(): - # If the account on the third party provider is already connected with another edX account, - # we display a message to the user. - context['duplicate_provider'] = pipeline.get_duplicate_provider(messages.get_messages(request)) - - auth_states = pipeline.get_provider_user_states(user) - - context['auth']['providers'] = [{ - 'id': state.provider.provider_id, - 'name': state.provider.name, # The name of the provider e.g. Facebook - 'connected': state.has_account, # Whether the user's edX account is connected with the provider. - # If the user is not connected, they should be directed to this page to authenticate - # with the particular provider, as long as the provider supports initiating a login. - 'connect_url': pipeline.get_login_url( - 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'), - ), - 'accepts_logins': state.provider.accepts_logins, - # If the user is connected, sending a POST request to this url removes the connection - # information for this provider from their edX account. - 'disconnect_url': pipeline.get_disconnect_url(state.provider.provider_id, state.association_id), - # We only want to include providers if they are either currently available to be logged - # in with, or if the user is already authenticated with them. - } for state in auth_states if state.provider.display_for_login or state.has_account] - - return context - - -def get_user_orders(user): - """Given a user, get the detail of all the orders from the Ecommerce service. - - Args: - user (User): The user to authenticate as when requesting ecommerce. - - Returns: - list of dict, representing orders returned by the Ecommerce service. - """ - user_orders = [] - commerce_configuration = CommerceConfiguration.current() - user_query = {'username': user.username} - - use_cache = commerce_configuration.is_cache_enabled - cache_key = commerce_configuration.CACHE_KEY + '.' + str(user.id) if use_cache else None - commerce_user_orders = get_api_data( - commerce_configuration, - 'orders', - api_client=get_ecommerce_api_client(user), - base_api_url=get_ecommerce_api_base_url(), - querystring=user_query, - cache_key=cache_key - ) - - for order in commerce_user_orders: - if order['status'].lower() == 'complete': - date_placed = datetime.strptime(order['date_placed'], "%Y-%m-%dT%H:%M:%SZ") - order_data = { - 'number': order['number'], - 'price': order['total_excl_tax'], - 'order_date': strftime_localized(date_placed, 'SHORT_DATE'), - 'receipt_url': EcommerceService().get_receipt_page_url(order['number']), - 'lines': order['lines'], - } - user_orders.append(order_data) - - return user_orders - - -def _get_extended_profile_fields(): - """Retrieve the extended profile fields from site configuration to be shown on the - Account Settings page - - Returns: - A list of dicts. Each dict corresponds to a single field. The keys per field are: - "field_name" : name of the field stored in user_profile.meta - "field_label" : The label of the field. - "field_type" : TextField or ListField - "field_options": a list of tuples for options in the dropdown in case of ListField - """ - - extended_profile_fields = [] - fields_already_showing = ['username', 'name', 'email', 'pref-lang', 'country', 'time_zone', 'level_of_education', - 'gender', 'year_of_birth', 'language_proficiencies', 'social_links'] - - field_labels_map = { - "first_name": _("First Name"), - "last_name": _("Last Name"), - "city": _("City"), - "state": _("State/Province/Region"), - "company": _("Company"), - "title": _("Title"), - "job_title": _("Job Title"), - "mailing_address": _("Mailing address"), - "goals": _("Tell us why you're interested in {platform_name}").format( - platform_name=configuration_helpers.get_value("PLATFORM_NAME", settings.PLATFORM_NAME) - ), - "profession": _("Profession"), - "specialty": _("Specialty") - } - - extended_profile_field_names = configuration_helpers.get_value('extended_profile_fields', []) - for field_to_exclude in fields_already_showing: - if field_to_exclude in extended_profile_field_names: - extended_profile_field_names.remove(field_to_exclude) - - extended_profile_field_options = configuration_helpers.get_value('EXTRA_FIELD_OPTIONS', []) - extended_profile_field_option_tuples = {} - for field in extended_profile_field_options.keys(): - field_options = extended_profile_field_options[field] - extended_profile_field_option_tuples[field] = [(option.lower(), option) for option in field_options] - - for field in extended_profile_field_names: - field_dict = { - "field_name": field, - "field_label": field_labels_map.get(field, field), - } - - field_options = extended_profile_field_option_tuples.get(field) - if field_options: - field_dict["field_type"] = "ListField" - field_dict["field_options"] = field_options - else: - field_dict["field_type"] = "TextField" - extended_profile_fields.append(field_dict) - - return extended_profile_fields diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_filters.py b/openedx/core/djangoapps/user_api/accounts/tests/test_filters.py deleted file mode 100644 index 782549aea0b5..000000000000 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_filters.py +++ /dev/null @@ -1,241 +0,0 @@ -""" -Test that various filters are fired for views in the certificates app. -""" -from django.http import HttpResponse -from django.test import override_settings -from django.urls import reverse -from openedx_filters import PipelineStep -from openedx_filters.learning.filters import AccountSettingsRenderStarted -from rest_framework import status -from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase - -from openedx.core.djangolib.testing.utils import skip_unless_lms -from common.djangoapps.student.tests.factories import UserFactory - - -class TestRenderInvalidAccountSettings(PipelineStep): - """ - Utility class used when getting steps for pipeline. - """ - - def run_filter(self, context, template_name): # pylint: disable=arguments-differ - """ - Pipeline step that stops the course about render process. - """ - raise AccountSettingsRenderStarted.RenderInvalidAccountSettings( - "You can't access the account settings page.", - account_settings_template="static_templates/server-error.html", - ) - - -class TestRedirectToPage(PipelineStep): - """ - Utility class used when getting steps for pipeline. - """ - - def run_filter(self, context, template_name): # pylint: disable=arguments-differ - """ - Pipeline step that redirects to dashboard before rendering the account settings page. - - When raising RedirectToPage, this filter uses a redirect_to field handled by - the course about view that redirects to that URL. - """ - raise AccountSettingsRenderStarted.RedirectToPage( - "You can't access this page, redirecting to dashboard.", - redirect_to="/courses", - ) - - -class TestRedirectToDefaultPage(PipelineStep): - """ - Utility class used when getting steps for pipeline. - """ - - def run_filter(self, context, template_name): # pylint: disable=arguments-differ - """ - Pipeline step that redirects to dashboard before rendering the account settings page. - - When raising RedirectToPage, this filter uses a redirect_to field handled by - the course about view that redirects to that URL. - """ - raise AccountSettingsRenderStarted.RedirectToPage( - "You can't access this page, redirecting to dashboard." - ) - - -class TestRenderCustomResponse(PipelineStep): - """ - Utility class used when getting steps for pipeline. - """ - - def run_filter(self, context, template_name): # pylint: disable=arguments-differ - """Pipeline step that returns a custom response when rendering the account settings page.""" - response = HttpResponse("Here's the text of the web page.") - raise AccountSettingsRenderStarted.RenderCustomResponse( - "You can't access this page.", - response=response, - ) - - -class TestAccountSettingsRender(PipelineStep): - """ - Utility class used when getting steps for pipeline. - """ - - def run_filter(self, context, template_name): # pylint: disable=arguments-differ - """Pipeline step that returns a custom response when rendering the account settings page.""" - template_name = 'static_templates/about.html' - return { - "context": context, "template_name": template_name, - } - - -@skip_unless_lms -class TestAccountSettingsFilters(SharedModuleStoreTestCase): - """ - Tests for the Open edX Filters associated with the account settings proccess. - - This class guarantees that the following filters are triggered during the user's account settings rendering: - - - AccountSettingsRenderStarted - """ - def setUp(self): # pylint: disable=arguments-differ - super().setUp() - self.user = UserFactory.create( - username="somestudent", - first_name="Student", - last_name="Person", - email="robot@robot.org", - is_active=True, - password="password", - ) - self.client.login(username=self.user.username, password="password") - self.account_settings_url = '/account/settings' - - @override_settings( - OPEN_EDX_FILTERS_CONFIG={ - "org.openedx.learning.student.settings.render.started.v1": { - "pipeline": [ - "openedx.core.djangoapps.user_api.accounts.tests.test_filters.TestAccountSettingsRender", - ], - "fail_silently": False, - }, - }, - ) - def test_account_settings_render_filter_executed(self): - """ - Test whether the account settings filter is triggered before the user's - account settings page is rendered. - - Expected result: - - AccountSettingsRenderStarted is triggered and executes TestAccountSettingsRender - """ - response = self.client.get(self.account_settings_url) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertContains(response, "This page left intentionally blank. Feel free to add your own content.") - - @override_settings( - OPEN_EDX_FILTERS_CONFIG={ - "org.openedx.learning.student.settings.render.started.v1": { - "pipeline": [ - "openedx.core.djangoapps.user_api.accounts.tests.test_filters.TestRenderInvalidAccountSettings", # pylint: disable=line-too-long - ], - "fail_silently": False, - }, - }, - PLATFORM_NAME="My site", - ) - def test_account_settings_render_alternative(self): - """ - Test whether the account settings filter is triggered before the user's - account settings page is rendered. - - Expected result: - - AccountSettingsRenderStarted is triggered and executes TestRenderInvalidAccountSettings # pylint: disable=line-too-long - """ - response = self.client.get(self.account_settings_url) - - self.assertContains(response, "There has been a 500 error on the My site servers") - - @override_settings( - OPEN_EDX_FILTERS_CONFIG={ - "org.openedx.learning.student.settings.render.started.v1": { - "pipeline": [ - "openedx.core.djangoapps.user_api.accounts.tests.test_filters.TestRenderCustomResponse", - ], - "fail_silently": False, - }, - }, - ) - def test_account_settings_render_custom_response(self): - """ - Test whether the account settings filter is triggered before the user's - account settings page is rendered. - - Expected result: - - AccountSettingsRenderStarted is triggered and executes TestRenderCustomResponse - """ - response = self.client.get(self.account_settings_url) - - self.assertEqual(response.content, b"Here's the text of the web page.") - - @override_settings( - OPEN_EDX_FILTERS_CONFIG={ - "org.openedx.learning.student.settings.render.started.v1": { - "pipeline": [ - "openedx.core.djangoapps.user_api.accounts.tests.test_filters.TestRedirectToPage", - ], - "fail_silently": False, - }, - }, - ) - def test_account_settings_redirect_to_page(self): - """ - Test whether the account settings filter is triggered before the user's - account settings page is rendered. - - Expected result: - - AccountSettingsRenderStarted is triggered and executes TestRedirectToPage - """ - response = self.client.get(self.account_settings_url) - - self.assertEqual(response.status_code, status.HTTP_302_FOUND) - self.assertEqual('/courses', response.url) - - @override_settings( - OPEN_EDX_FILTERS_CONFIG={ - "org.openedx.learning.student.settings.render.started.v1": { - "pipeline": [ - "openedx.core.djangoapps.user_api.accounts.tests.test_filters.TestRedirectToDefaultPage", - ], - "fail_silently": False, - }, - }, - ) - def test_account_settings_redirect_default(self): - """ - Test whether the account settings filter is triggered before the user's - account settings page is rendered. - - Expected result: - - AccountSettingsRenderStarted is triggered and executes TestRedirectToDefaultPage - """ - response = self.client.get(self.account_settings_url) - - self.assertEqual(response.status_code, status.HTTP_302_FOUND) - self.assertEqual(f"{reverse('dashboard')}", response.url) - - @override_settings(OPEN_EDX_FILTERS_CONFIG={}) - def test_account_settings_render_without_filter_config(self): - """ - Test whether the course about filter is triggered before the course about - render without affecting its execution flow. - - Expected result: - - AccountSettingsRenderStarted executes a noop (empty pipeline). Without any - modification comparing it with the effects of TestAccountSettingsRender. - - The view response is HTTP_200_OK. - """ - response = self.client.get(self.account_settings_url) - - self.assertNotContains(response, "This page left intentionally blank. Feel free to add your own content.") diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_settings_views.py b/openedx/core/djangoapps/user_api/accounts/tests/test_settings_views.py deleted file mode 100644 index b49be866ff4c..000000000000 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_settings_views.py +++ /dev/null @@ -1,271 +0,0 @@ -""" Tests for views related to account settings. """ - - -from unittest import mock -from django.conf import settings -from django.contrib import messages -from django.contrib.messages.middleware import MessageMiddleware -from django.http import HttpRequest -from django.test import TestCase -from django.test.utils import override_settings -from django.urls import reverse -from edx_rest_api_client import exceptions - -from edx_toggles.toggles.testutils import override_waffle_flag -from lms.djangoapps.commerce.models import CommerceConfiguration -from lms.djangoapps.commerce.tests import factories -from lms.djangoapps.commerce.tests.mocks import mock_get_orders -from openedx.core.djangoapps.dark_lang.models import DarkLangConfig -from openedx.core.djangoapps.lang_pref.tests.test_api import EN, LT_LT -from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin -from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory -from openedx.core.djangoapps.site_configuration.tests.mixins import SiteMixin -from openedx.core.djangoapps.user_api.accounts.settings_views import account_settings_context, get_user_orders -from openedx.core.djangoapps.user_api.accounts.toggles import REDIRECT_TO_ACCOUNT_MICROFRONTEND -from openedx.core.djangoapps.user_api.tests.factories import UserPreferenceFactory -from openedx.core.djangolib.testing.utils import skip_unless_lms -from openedx.features.enterprise_support.utils import get_enterprise_readonly_account_fields -from common.djangoapps.student.tests.factories import UserFactory -from common.djangoapps.third_party_auth.tests.testutil import ThirdPartyAuthTestMixin - - -@skip_unless_lms -class AccountSettingsViewTest(ThirdPartyAuthTestMixin, SiteMixin, ProgramsApiConfigMixin, TestCase): - """ Tests for the account settings view. """ - - USERNAME = 'student' - PASSWORD = 'password' - FIELDS = [ - 'country', - 'gender', - 'language', - 'level_of_education', - 'password', - 'year_of_birth', - 'preferred_language', - 'time_zone', - ] - - @mock.patch("django.conf.settings.MESSAGE_STORAGE", 'django.contrib.messages.storage.cookie.CookieStorage') - def setUp(self): # pylint: disable=arguments-differ - super().setUp() - self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD) - CommerceConfiguration.objects.create(cache_ttl=10, enabled=True) - self.client.login(username=self.USERNAME, password=self.PASSWORD) - - self.request = HttpRequest() - self.request.user = self.user - - # For these tests, two third party auth providers are enabled by default: - self.configure_google_provider(enabled=True, visible=True) - self.configure_facebook_provider(enabled=True, visible=True) - - # Python-social saves auth failure notifcations in Django messages. - # See pipeline.get_duplicate_provider() for details. - self.request.COOKIES = {} - MessageMiddleware(get_response=lambda request: None).process_request(self.request) - messages.error(self.request, 'Facebook is already in use.', extra_tags='Auth facebook') - - @mock.patch('openedx.features.enterprise_support.api.enterprise_customer_for_request') - def test_context(self, mock_enterprise_customer_for_request): - self.request.site = SiteFactory.create() - UserPreferenceFactory(user=self.user, key='pref-lang', value='lt-lt') - DarkLangConfig( - released_languages='en', - changed_by=self.user, - enabled=True, - beta_languages='lt-lt', - enable_beta_languages=True - ).save() - mock_enterprise_customer_for_request.return_value = {} - - with override_settings(LANGUAGES=[EN, LT_LT], LANGUAGE_CODE='en'): - context = account_settings_context(self.request) - - user_accounts_api_url = reverse("accounts_api", kwargs={'username': self.user.username}) - assert context['user_accounts_api_url'] == user_accounts_api_url - - user_preferences_api_url = reverse('preferences_api', kwargs={'username': self.user.username}) - assert context['user_preferences_api_url'] == user_preferences_api_url - - for attribute in self.FIELDS: - assert attribute in context['fields'] - - assert context['user_accounts_api_url'] == reverse('accounts_api', kwargs={'username': self.user.username}) - assert context['user_preferences_api_url'] ==\ - reverse('preferences_api', kwargs={'username': self.user.username}) - - assert context['duplicate_provider'] == 'facebook' - assert context['auth']['providers'][0]['name'] == 'Facebook' - assert context['auth']['providers'][1]['name'] == 'Google' - - assert context['sync_learner_profile_data'] is False - assert context['edx_support_url'] == settings.SUPPORT_SITE_LINK - assert context['enterprise_name'] is None - assert context['enterprise_readonly_account_fields'] ==\ - {'fields': list(get_enterprise_readonly_account_fields(self.user))} - - expected_beta_language = {'code': 'lt-lt', 'name': settings.LANGUAGE_DICT.get('lt-lt')} - assert context['beta_language'] == expected_beta_language - - @mock.patch('openedx.core.djangoapps.user_api.accounts.settings_views.enterprise_customer_for_request') - @mock.patch('openedx.features.enterprise_support.utils.third_party_auth.provider.Registry.get') - def test_context_for_enterprise_learner( - self, mock_get_auth_provider, mock_enterprise_customer_for_request - ): - dummy_enterprise_customer = { - 'uuid': 'real-ent-uuid', - 'name': 'Dummy Enterprise', - 'identity_provider': 'saml-ubc' - } - mock_enterprise_customer_for_request.return_value = dummy_enterprise_customer - self.request.site = SiteFactory.create() - mock_get_auth_provider.return_value.sync_learner_profile_data = True - context = account_settings_context(self.request) - - user_accounts_api_url = reverse("accounts_api", kwargs={'username': self.user.username}) - assert context['user_accounts_api_url'] == user_accounts_api_url - - user_preferences_api_url = reverse('preferences_api', kwargs={'username': self.user.username}) - assert context['user_preferences_api_url'] == user_preferences_api_url - - for attribute in self.FIELDS: - assert attribute in context['fields'] - - assert context['user_accounts_api_url'] == reverse('accounts_api', kwargs={'username': self.user.username}) - assert context['user_preferences_api_url'] ==\ - reverse('preferences_api', kwargs={'username': self.user.username}) - - assert context['duplicate_provider'] == 'facebook' - assert context['auth']['providers'][0]['name'] == 'Facebook' - assert context['auth']['providers'][1]['name'] == 'Google' - - assert context['sync_learner_profile_data'] == mock_get_auth_provider.return_value.sync_learner_profile_data - assert context['edx_support_url'] == settings.SUPPORT_SITE_LINK - assert context['enterprise_name'] == dummy_enterprise_customer['name'] - assert context['enterprise_readonly_account_fields'] ==\ - {'fields': list(get_enterprise_readonly_account_fields(self.user))} - - def test_view(self): - """ - Test that all fields are visible - """ - view_path = reverse('account_settings') - response = self.client.get(path=view_path) - - for attribute in self.FIELDS: - self.assertContains(response, attribute) - - def test_header_with_programs_listing_enabled(self): - """ - Verify that tabs header will be shown while program listing is enabled. - """ - self.create_programs_config() - view_path = reverse('account_settings') - response = self.client.get(path=view_path) - - self.assertContains(response, 'global-header') - - def test_header_with_programs_listing_disabled(self): - """ - Verify that nav header will be shown while program listing is disabled. - """ - self.create_programs_config(enabled=False) - view_path = reverse('account_settings') - response = self.client.get(path=view_path) - - self.assertContains(response, 'global-header') - - def test_commerce_order_detail(self): - """ - Verify that get_user_orders returns the correct order data. - """ - with mock_get_orders(): - order_detail = get_user_orders(self.user) - - for i, order in enumerate(mock_get_orders.default_response['results']): - expected = { - 'number': order['number'], - 'price': order['total_excl_tax'], - 'order_date': 'Jan 01, 2016', - 'receipt_url': '/checkout/receipt/?order_number=' + order['number'], - 'lines': order['lines'], - } - assert order_detail[i] == expected - - def test_commerce_order_detail_exception(self): - with mock_get_orders(exception=exceptions.HttpNotFoundError): - order_detail = get_user_orders(self.user) - - assert not order_detail - - def test_incomplete_order_detail(self): - response = { - 'results': [ - factories.OrderFactory( - status='Incomplete', - lines=[ - factories.OrderLineFactory( - product=factories.ProductFactory(attribute_values=[factories.ProductAttributeFactory()]) - ) - ] - ) - ] - } - with mock_get_orders(response=response): - order_detail = get_user_orders(self.user) - - assert not order_detail - - def test_order_history_with_no_product(self): - response = { - 'results': [ - factories.OrderFactory( - lines=[ - factories.OrderLineFactory( - product=None - ), - factories.OrderLineFactory( - product=factories.ProductFactory(attribute_values=[factories.ProductAttributeFactory( - name='certificate_type', - value='verified' - )]) - ) - ] - ) - ] - } - with mock_get_orders(response=response): - order_detail = get_user_orders(self.user) - - assert len(order_detail) == 1 - - def test_redirect_view(self): - old_url_path = reverse('account_settings') - with override_waffle_flag(REDIRECT_TO_ACCOUNT_MICROFRONTEND, active=True): - # Test with waffle flag active and none site setting, redirects to microfrontend - response = self.client.get(path=old_url_path) - self.assertRedirects(response, settings.ACCOUNT_MICROFRONTEND_URL, fetch_redirect_response=False) - - # Test with waffle flag disabled and site setting disabled, does not redirect - response = self.client.get(path=old_url_path) - for attribute in self.FIELDS: - self.assertContains(response, attribute) - - # Test with site setting disabled, does not redirect - site_domain = 'othersite.example.com' - site = self.set_up_site(site_domain, { - 'SITE_NAME': site_domain, - 'ENABLE_ACCOUNT_MICROFRONTEND': False - }) - self.client.login(username=self.USERNAME, password=self.PASSWORD) - response = self.client.get(path=old_url_path) - for attribute in self.FIELDS: - self.assertContains(response, attribute) - - # Test with site setting enabled, redirects to microfrontend - site.configuration.site_values['ENABLE_ACCOUNT_MICROFRONTEND'] = True - site.configuration.save() - site.__class__.objects.clear_cache() - response = self.client.get(path=old_url_path) - self.assertRedirects(response, settings.ACCOUNT_MICROFRONTEND_URL, fetch_redirect_response=False) diff --git a/openedx/core/djangoapps/user_api/accounts/toggles.py b/openedx/core/djangoapps/user_api/accounts/toggles.py deleted file mode 100644 index 80de4fa75692..000000000000 --- a/openedx/core/djangoapps/user_api/accounts/toggles.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -Toggles for accounts related code. -""" - -from edx_toggles.toggles import WaffleFlag - -from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers - -# .. toggle_name: order_history.redirect_to_microfrontend -# .. toggle_implementation: WaffleFlag -# .. toggle_default: False -# .. toggle_description: Supports staged rollout of a new micro-frontend-based implementation of the order history page. -# .. toggle_use_cases: temporary, open_edx -# .. toggle_creation_date: 2019-04-11 -# .. toggle_target_removal_date: 2020-12-31 -# .. toggle_warning: Also set settings.ORDER_HISTORY_MICROFRONTEND_URL and site's -# ENABLE_ORDER_HISTORY_MICROFRONTEND. -# .. toggle_tickets: DEPR-17 -REDIRECT_TO_ORDER_HISTORY_MICROFRONTEND = WaffleFlag('order_history.redirect_to_microfrontend', __name__) - - -def should_redirect_to_order_history_microfrontend(): - return ( - configuration_helpers.get_value('ENABLE_ORDER_HISTORY_MICROFRONTEND') and - REDIRECT_TO_ORDER_HISTORY_MICROFRONTEND.is_enabled() - ) - - -# .. toggle_name: account.redirect_to_microfrontend -# .. toggle_implementation: WaffleFlag -# .. toggle_default: False -# .. toggle_description: Supports staged rollout of a new micro-frontend-based implementation of the account page. -# Its action can be overridden using site's ENABLE_ACCOUNT_MICROFRONTEND setting. -# .. toggle_use_cases: temporary, open_edx -# .. toggle_creation_date: 2019-04-30 -# .. toggle_target_removal_date: 2021-12-31 -# .. toggle_warning: Also set settings.ACCOUNT_MICROFRONTEND_URL. -# .. toggle_tickets: DEPR-17 -REDIRECT_TO_ACCOUNT_MICROFRONTEND = WaffleFlag('account.redirect_to_microfrontend', __name__) - - -def should_redirect_to_account_microfrontend(): - return configuration_helpers.get_value('ENABLE_ACCOUNT_MICROFRONTEND', - REDIRECT_TO_ACCOUNT_MICROFRONTEND.is_enabled()) diff --git a/openedx/core/djangoapps/user_api/legacy_urls.py b/openedx/core/djangoapps/user_api/legacy_urls.py index b3f707f64b50..ad02f7f19ce8 100644 --- a/openedx/core/djangoapps/user_api/legacy_urls.py +++ b/openedx/core/djangoapps/user_api/legacy_urls.py @@ -5,7 +5,6 @@ from rest_framework import routers from . import views as user_api_views -from .accounts.settings_views import account_settings from .models import UserPreference USER_API_ROUTER = routers.DefaultRouter() @@ -13,7 +12,6 @@ USER_API_ROUTER.register(r'user_prefs', user_api_views.UserPreferenceViewSet) urlpatterns = [ - path('account/settings', account_settings, name='account_settings'), path('user_api/v1/', include(USER_API_ROUTER.urls)), re_path( fr'^user_api/v1/preferences/(?P{UserPreference.KEY_REGEX})/users/$', diff --git a/openedx/core/djangoapps/user_authn/cookies.py b/openedx/core/djangoapps/user_authn/cookies.py index 24f929698fa7..036baf2125b2 100644 --- a/openedx/core/djangoapps/user_authn/cookies.py +++ b/openedx/core/djangoapps/user_authn/cookies.py @@ -6,6 +6,7 @@ import json import logging import time +from urllib.parse import urljoin from django.conf import settings from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user @@ -244,8 +245,8 @@ def _get_user_info_cookie_data(request, user): # External sites will need to have fallback mechanisms to handle this case # (most likely just hiding the links). try: - header_urls['account_settings'] = reverse('account_settings') - header_urls['learner_profile'] = reverse('learner_profile', kwargs={'username': user.username}) + header_urls['account_settings'] = settings.ACCOUNT_MICROFRONTEND_URL + header_urls['learner_profile'] = urljoin(settings.PROFILE_MICROFRONTEND_URL, f'/u/{user.username}') except NoReverseMatch: pass diff --git a/openedx/core/djangoapps/user_authn/tests/test_cookies.py b/openedx/core/djangoapps/user_authn/tests/test_cookies.py index a90f20f19469..826ef1e1209a 100644 --- a/openedx/core/djangoapps/user_authn/tests/test_cookies.py +++ b/openedx/core/djangoapps/user_authn/tests/test_cookies.py @@ -4,6 +4,7 @@ from datetime import date import json from unittest.mock import MagicMock, patch +from urllib.parse import urljoin from django.conf import settings from django.http import HttpResponse from django.test import RequestFactory, TestCase @@ -57,8 +58,8 @@ def _get_expected_image_urls(self): def _get_expected_header_urls(self): expected_header_urls = { 'logout': reverse('logout'), - 'account_settings': reverse('account_settings'), - 'learner_profile': reverse('learner_profile', kwargs={'username': self.user.username}), + 'account_settings': settings.ACCOUNT_MICROFRONTEND_URL, + 'learner_profile': urljoin(settings.PROFILE_MICROFRONTEND_URL, f'/u/{self.user.username}'), } block_url = retrieve_last_sitewide_block_completed(self.user) if block_url: diff --git a/openedx/core/djangoapps/user_authn/views/tests/test_login.py b/openedx/core/djangoapps/user_authn/views/tests/test_login.py index 1e8a4c3ed510..a8a591083fae 100644 --- a/openedx/core/djangoapps/user_authn/views/tests/test_login.py +++ b/openedx/core/djangoapps/user_authn/views/tests/test_login.py @@ -496,7 +496,7 @@ def test_login_user_info_cookie(self): # Check that the URLs are absolute for url in user_info["header_urls"].values(): - assert 'http://testserver/' in url + assert 'http://' in url def test_logout_deletes_mktg_cookies(self): response, _ = self._login_response(self.user_email, self.password) diff --git a/openedx/features/learner_profile/README.rst b/openedx/features/learner_profile/README.rst deleted file mode 100644 index 0dce8e10ccdc..000000000000 --- a/openedx/features/learner_profile/README.rst +++ /dev/null @@ -1,8 +0,0 @@ -Learner Profile ---------------- - -This directory contains a Django application that provides a view to render -a profile for any Open edX learner. See `Exploring Your Dashboard and Profile`_ -for more details. - -.. _Exploring Your Dashboard and Profile: https://edx.readthedocs.io/projects/open-edx-learner-guide/en/latest/SFD_dashboard_profile_SectionHead.html?highlight=profile diff --git a/openedx/features/learner_profile/__init__.py b/openedx/features/learner_profile/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/openedx/features/learner_profile/static/learner_profile/fixtures/learner_profile.html b/openedx/features/learner_profile/static/learner_profile/fixtures/learner_profile.html deleted file mode 100644 index 61c139210a20..000000000000 --- a/openedx/features/learner_profile/static/learner_profile/fixtures/learner_profile.html +++ /dev/null @@ -1,40 +0,0 @@ -
-
-
- - -
-
-

- - - - - Loading - -

-
- -
diff --git a/openedx/features/learner_profile/static/learner_profile/js/learner_profile_factory.js b/openedx/features/learner_profile/static/learner_profile/js/learner_profile_factory.js deleted file mode 100644 index 6419dd17703a..000000000000 --- a/openedx/features/learner_profile/static/learner_profile/js/learner_profile_factory.js +++ /dev/null @@ -1,246 +0,0 @@ -(function(define) { - 'use strict'; - - define([ - 'gettext', - 'jquery', - 'underscore', - 'backbone', - 'logger', - 'edx-ui-toolkit/js/utils/string-utils', - 'edx-ui-toolkit/js/pagination/paging-collection', - 'js/student_account/models/user_account_model', - 'js/student_account/models/user_preferences_model', - 'js/views/fields', - 'learner_profile/js/views/learner_profile_fields', - 'learner_profile/js/views/learner_profile_view', - 'learner_profile/js/models/badges_model', - 'learner_profile/js/views/badge_list_container', - 'js/student_account/views/account_settings_fields', - 'js/views/message_banner', - 'string_utils' - ], function(gettext, $, _, Backbone, Logger, StringUtils, PagingCollection, AccountSettingsModel, - AccountPreferencesModel, FieldsView, LearnerProfileFieldsView, LearnerProfileView, BadgeModel, - BadgeListContainer, AccountSettingsFieldViews, MessageBannerView) { - return function(options) { - var $learnerProfileElement = $('.wrapper-profile'); - - var accountSettingsModel = new AccountSettingsModel( - _.extend( - options.account_settings_data, - { - default_public_account_fields: options.default_public_account_fields, - parental_consent_age_limit: options.parental_consent_age_limit, - enable_coppa_compliance: options.enable_coppa_compliance - } - ), - {parse: true} - ); - var AccountPreferencesModelWithDefaults = AccountPreferencesModel.extend({ - defaults: { - account_privacy: options.default_visibility - } - }); - var accountPreferencesModel = new AccountPreferencesModelWithDefaults(options.preferences_data); - - var editable = options.own_profile ? 'toggle' : 'never'; - - var messageView = new MessageBannerView({ - el: $('.message-banner') - }); - - var accountPrivacyFieldView, - profileImageFieldView, - usernameFieldView, - nameFieldView, - sectionOneFieldViews, - sectionTwoFieldViews, - BadgeCollection, - badgeCollection, - badgeListContainer, - learnerProfileView, - getProfileVisibility, - showLearnerProfileView; - - accountSettingsModel.url = options.accounts_api_url; - accountPreferencesModel.url = options.preferences_api_url; - - accountPrivacyFieldView = new LearnerProfileFieldsView.AccountPrivacyFieldView({ - model: accountPreferencesModel, - required: true, - editable: 'always', - showMessages: false, - title: gettext('Profile Visibility:'), - valueAttribute: 'account_privacy', - options: [ - ['private', gettext('Limited Profile')], - ['all_users', gettext('Full Profile')] - ], - helpMessage: '', - accountSettingsPageUrl: options.account_settings_page_url, - persistChanges: true - }); - - profileImageFieldView = new LearnerProfileFieldsView.ProfileImageFieldView({ - model: accountSettingsModel, - valueAttribute: 'profile_image', - editable: editable === 'toggle', - messageView: messageView, - imageMaxBytes: options.profile_image_max_bytes, - imageMinBytes: options.profile_image_min_bytes, - imageUploadUrl: options.profile_image_upload_url, - imageRemoveUrl: options.profile_image_remove_url - }); - - usernameFieldView = new FieldsView.ReadonlyFieldView({ - model: accountSettingsModel, - screenReaderTitle: gettext('Username'), - valueAttribute: 'username', - helpMessage: '' - }); - - nameFieldView = new FieldsView.ReadonlyFieldView({ - model: accountSettingsModel, - screenReaderTitle: gettext('Full Name'), - valueAttribute: 'name', - helpMessage: '' - }); - - sectionOneFieldViews = [ - new LearnerProfileFieldsView.SocialLinkIconsView({ - model: accountSettingsModel, - socialPlatforms: options.social_platforms, - ownProfile: options.own_profile - }), - - new FieldsView.DateFieldView({ - title: gettext('Joined'), - titleVisible: true, - model: accountSettingsModel, - screenReaderTitle: gettext('Joined Date'), - valueAttribute: 'date_joined', - helpMessage: '', - userLanguage: accountSettingsModel.get('language'), - userTimezone: accountPreferencesModel.get('time_zone'), - dateFormat: 'MMMM YYYY' // not localized, but hopefully ok. - }), - - new FieldsView.DropdownFieldView({ - title: gettext('Location'), - titleVisible: true, - model: accountSettingsModel, - screenReaderTitle: gettext('Country'), - required: true, - editable: editable, - showMessages: false, - placeholderValue: gettext('Add Country'), - valueAttribute: 'country', - options: options.country_options, - helpMessage: '', - persistChanges: true - }), - - new AccountSettingsFieldViews.LanguageProficienciesFieldView({ - title: gettext('Language'), - titleVisible: true, - model: accountSettingsModel, - screenReaderTitle: gettext('Preferred Language'), - required: false, - editable: editable, - showMessages: false, - placeholderValue: gettext('Add language'), - valueAttribute: 'language_proficiencies', - options: options.language_options, - helpMessage: '', - persistChanges: true - }) - ]; - - sectionTwoFieldViews = [ - new FieldsView.TextareaFieldView({ - model: accountSettingsModel, - editable: editable, - showMessages: false, - title: gettext('About me'), - // eslint-disable-next-line max-len - placeholderValue: gettext("Tell other learners a little about yourself: where you live, what your interests are, why you're taking courses, or what you hope to learn."), - valueAttribute: 'bio', - helpMessage: '', - persistChanges: true, - messagePosition: 'header', - maxCharacters: 300 - }) - ]; - - BadgeCollection = PagingCollection.extend({ - queryParams: { - currentPage: 'current_page' - } - }); - badgeCollection = new BadgeCollection(); - badgeCollection.url = options.badges_api_url; - - badgeListContainer = new BadgeListContainer({ - attributes: {class: 'badge-set-display'}, - collection: badgeCollection, - find_courses_url: options.find_courses_url, - ownProfile: options.own_profile, - badgeMeta: { - badges_logo: options.badges_logo, - backpack_ui_img: options.backpack_ui_img, - badges_icon: options.badges_icon - } - }); - - learnerProfileView = new LearnerProfileView({ - el: $learnerProfileElement, - ownProfile: options.own_profile, - has_preferences_access: options.has_preferences_access, - accountSettingsModel: accountSettingsModel, - preferencesModel: accountPreferencesModel, - accountPrivacyFieldView: accountPrivacyFieldView, - profileImageFieldView: profileImageFieldView, - usernameFieldView: usernameFieldView, - nameFieldView: nameFieldView, - sectionOneFieldViews: sectionOneFieldViews, - sectionTwoFieldViews: sectionTwoFieldViews, - badgeListContainer: badgeListContainer, - platformName: options.platform_name - }); - - getProfileVisibility = function() { - if (options.has_preferences_access) { - return accountPreferencesModel.get('account_privacy'); - } else { - return accountSettingsModel.get('profile_is_public') ? 'all_users' : 'private'; - } - }; - - showLearnerProfileView = function() { - // Record that the profile page was viewed - Logger.log('edx.user.settings.viewed', { - page: 'profile', - visibility: getProfileVisibility(), - user_id: options.profile_user_id - }); - - // Render the view for the first time - learnerProfileView.render(); - }; - - if (options.has_preferences_access) { - if (accountSettingsModel.get('requires_parental_consent')) { - accountPreferencesModel.set('account_privacy', 'private'); - } - } - showLearnerProfileView(); - - return { - accountSettingsModel: accountSettingsModel, - accountPreferencesModel: accountPreferencesModel, - learnerProfileView: learnerProfileView, - badgeListContainer: badgeListContainer - }; - }; - }); -}).call(this, define || RequireJS.define); diff --git a/openedx/features/learner_profile/static/learner_profile/js/models/badges_model.js b/openedx/features/learner_profile/static/learner_profile/js/models/badges_model.js deleted file mode 100644 index 42da19ef7677..000000000000 --- a/openedx/features/learner_profile/static/learner_profile/js/models/badges_model.js +++ /dev/null @@ -1,8 +0,0 @@ -(function(define) { - 'use strict'; - - define(['backbone'], function(Backbone) { - var BadgesModel = Backbone.Model.extend({}); - return BadgesModel; - }); -}).call(this, define || RequireJS.define); diff --git a/openedx/features/learner_profile/static/learner_profile/js/spec/learner_profile_factory_spec.js b/openedx/features/learner_profile/static/learner_profile/js/spec/learner_profile_factory_spec.js deleted file mode 100644 index f5b8f4bec6b5..000000000000 --- a/openedx/features/learner_profile/static/learner_profile/js/spec/learner_profile_factory_spec.js +++ /dev/null @@ -1,222 +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', - 'learner_profile/js/spec_helpers/helpers', - 'js/views/fields', - 'js/student_account/models/user_account_model', - 'js/student_account/models/user_preferences_model', - 'learner_profile/js/views/learner_profile_view', - 'learner_profile/js/views/learner_profile_fields', - 'learner_profile/js/learner_profile_factory', - 'js/views/message_banner' - ], - function(Backbone, $, _, AjaxHelpers, TemplateHelpers, Helpers, LearnerProfileHelpers, FieldViews, - UserAccountModel, UserPreferencesModel, LearnerProfileView, LearnerProfileFields, LearnerProfilePage) { - 'use strict'; - - describe('edx.user.LearnerProfileFactory', function() { - var createProfilePage; - - beforeEach(function() { - loadFixtures('learner_profile/fixtures/learner_profile.html'); - }); - - afterEach(function() { - Backbone.history.stop(); - }); - - createProfilePage = function(ownProfile, options) { - return new LearnerProfilePage({ - accounts_api_url: Helpers.USER_ACCOUNTS_API_URL, - preferences_api_url: Helpers.USER_PREFERENCES_API_URL, - badges_api_url: Helpers.BADGES_API_URL, - own_profile: ownProfile, - account_settings_page_url: Helpers.USER_ACCOUNTS_API_URL, - country_options: Helpers.FIELD_OPTIONS, - language_options: Helpers.FIELD_OPTIONS, - has_preferences_access: true, - profile_image_max_bytes: Helpers.IMAGE_MAX_BYTES, - profile_image_min_bytes: Helpers.IMAGE_MIN_BYTES, - profile_image_upload_url: Helpers.IMAGE_UPLOAD_API_URL, - profile_image_remove_url: Helpers.IMAGE_REMOVE_API_URL, - default_visibility: 'all_users', - platform_name: 'edX', - find_courses_url: '/courses/', - account_settings_data: Helpers.createAccountSettingsData(options), - preferences_data: Helpers.createUserPreferencesData() - }); - }; - - it('renders the full profile for a user', function() { - var context, - learnerProfileView; - AjaxHelpers.requests(this); - context = createProfilePage(true); - learnerProfileView = context.learnerProfileView; - - // sets the profile for full view. - context.accountPreferencesModel.set({account_privacy: 'all_users'}); - LearnerProfileHelpers.expectProfileSectionsAndFieldsToBeRendered(learnerProfileView, false); - }); - - it("renders the limited profile for undefined 'year_of_birth'", function() { - var context = createProfilePage(true, {year_of_birth: '', requires_parental_consent: true}), - learnerProfileView = context.learnerProfileView; - - LearnerProfileHelpers.expectLimitedProfileSectionsAndFieldsToBeRendered(learnerProfileView); - }); - - it("doesn't show the mode toggle if badges are disabled", function() { - var requests = AjaxHelpers.requests(this), - context = createProfilePage(true, {accomplishments_shared: false}), - tabbedView = context.learnerProfileView.tabbedView, - learnerProfileView = context.learnerProfileView; - - LearnerProfileHelpers.expectTabbedViewToBeUndefined(requests, tabbedView); - LearnerProfileHelpers.expectBadgesHidden(learnerProfileView); - }); - - it("doesn't show the mode toggle if badges fail to fetch", function() { - var requests = AjaxHelpers.requests(this), - context = createProfilePage(true, {accomplishments_shared: false}), - tabbedView = context.learnerProfileView.tabbedView, - learnerProfileView = context.learnerProfileView; - - LearnerProfileHelpers.expectTabbedViewToBeUndefined(requests, tabbedView); - LearnerProfileHelpers.expectBadgesHidden(learnerProfileView); - }); - - it('renders the mode toggle if there are badges', function() { - var requests = AjaxHelpers.requests(this), - context = createProfilePage(true, {accomplishments_shared: true}), - tabbedView = context.learnerProfileView.tabbedView; - - AjaxHelpers.expectRequest(requests, 'POST', '/event'); - AjaxHelpers.respondWithError(requests, 404); - AjaxHelpers.respondWithJson(requests, LearnerProfileHelpers.firstPageBadges); - - LearnerProfileHelpers.expectTabbedViewToBeShown(tabbedView); - }); - - it('renders the mode toggle if badges enabled but none exist', function() { - var requests = AjaxHelpers.requests(this), - context = createProfilePage(true, {accomplishments_shared: true}), - tabbedView = context.learnerProfileView.tabbedView; - - AjaxHelpers.expectRequest(requests, 'POST', '/event'); - AjaxHelpers.respondWithError(requests, 404); - AjaxHelpers.respondWithJson(requests, LearnerProfileHelpers.emptyBadges); - - LearnerProfileHelpers.expectTabbedViewToBeShown(tabbedView); - }); - - it('displays the badges when the accomplishments toggle is selected', function() { - var requests = AjaxHelpers.requests(this), - context = createProfilePage(true, {accomplishments_shared: true}), - learnerProfileView = context.learnerProfileView, - tabbedView = learnerProfileView.tabbedView; - - AjaxHelpers.expectRequest(requests, 'POST', '/event'); - AjaxHelpers.respondWithError(requests, 404); - AjaxHelpers.respondWithJson(requests, LearnerProfileHelpers.secondPageBadges); - - LearnerProfileHelpers.expectBadgesHidden(learnerProfileView); - tabbedView.$el.find('[data-url="accomplishments"]').click(); - LearnerProfileHelpers.expectBadgesDisplayed(learnerProfileView, 10, false); - tabbedView.$el.find('[data-url="about_me"]').click(); - LearnerProfileHelpers.expectBadgesHidden(learnerProfileView); - }); - - it('displays a placeholder on the last page of badges', function() { - var requests = AjaxHelpers.requests(this), - context = createProfilePage(true, {accomplishments_shared: true}), - learnerProfileView = context.learnerProfileView, - tabbedView = learnerProfileView.tabbedView; - - AjaxHelpers.expectRequest(requests, 'POST', '/event'); - AjaxHelpers.respondWithError(requests, 404); - AjaxHelpers.respondWithJson(requests, LearnerProfileHelpers.thirdPageBadges); - - LearnerProfileHelpers.expectBadgesHidden(learnerProfileView); - tabbedView.$el.find('[data-url="accomplishments"]').click(); - LearnerProfileHelpers.expectBadgesDisplayed(learnerProfileView, 10, true); - tabbedView.$el.find('[data-url="about_me"]').click(); - LearnerProfileHelpers.expectBadgesHidden(learnerProfileView); - }); - - it('displays a placeholder when the accomplishments toggle is selected and no badges exist', function() { - var requests = AjaxHelpers.requests(this), - context = createProfilePage(true, {accomplishments_shared: true}), - learnerProfileView = context.learnerProfileView, - tabbedView = learnerProfileView.tabbedView; - - AjaxHelpers.expectRequest(requests, 'POST', '/event'); - AjaxHelpers.respondWithError(requests, 404); - AjaxHelpers.respondWithJson(requests, LearnerProfileHelpers.emptyBadges); - - LearnerProfileHelpers.expectBadgesHidden(learnerProfileView); - tabbedView.$el.find('[data-url="accomplishments"]').click(); - LearnerProfileHelpers.expectBadgesDisplayed(learnerProfileView, 0, true); - tabbedView.$el.find('[data-url="about_me"]').click(); - LearnerProfileHelpers.expectBadgesHidden(learnerProfileView); - }); - - it('shows a paginated list of badges', function() { - var requests = AjaxHelpers.requests(this), - context = createProfilePage(true, {accomplishments_shared: true}), - learnerProfileView = context.learnerProfileView, - tabbedView = learnerProfileView.tabbedView; - - AjaxHelpers.expectRequest(requests, 'POST', '/event'); - AjaxHelpers.respondWithError(requests, 404); - AjaxHelpers.respondWithJson(requests, LearnerProfileHelpers.firstPageBadges); - - tabbedView.$el.find('[data-url="accomplishments"]').click(); - LearnerProfileHelpers.expectBadgesDisplayed(learnerProfileView, 10, false); - LearnerProfileHelpers.expectPage(learnerProfileView, LearnerProfileHelpers.firstPageBadges); - }); - - it('allows forward and backward navigation of badges', function() { - var requests = AjaxHelpers.requests(this), - context = createProfilePage(true, {accomplishments_shared: true}), - learnerProfileView = context.learnerProfileView, - tabbedView = learnerProfileView.tabbedView, - badgeListContainer = context.badgeListContainer; - - AjaxHelpers.expectRequest(requests, 'POST', '/event'); - AjaxHelpers.respondWithError(requests, 404); - AjaxHelpers.respondWithJson(requests, LearnerProfileHelpers.firstPageBadges); - - tabbedView.$el.find('[data-url="accomplishments"]').click(); - - badgeListContainer.$el.find('.next-page-link').click(); - AjaxHelpers.respondWithJson(requests, LearnerProfileHelpers.secondPageBadges); - LearnerProfileHelpers.expectPage(learnerProfileView, LearnerProfileHelpers.secondPageBadges); - - badgeListContainer.$el.find('.next-page-link').click(); - AjaxHelpers.respondWithJson(requests, LearnerProfileHelpers.thirdPageBadges); - LearnerProfileHelpers.expectBadgesDisplayed(learnerProfileView, 10, true); - LearnerProfileHelpers.expectPage(learnerProfileView, LearnerProfileHelpers.thirdPageBadges); - - badgeListContainer.$el.find('.previous-page-link').click(); - AjaxHelpers.respondWithJson(requests, LearnerProfileHelpers.secondPageBadges); - LearnerProfileHelpers.expectPage(learnerProfileView, LearnerProfileHelpers.secondPageBadges); - LearnerProfileHelpers.expectBadgesDisplayed(learnerProfileView, 10, false); - - badgeListContainer.$el.find('.previous-page-link').click(); - AjaxHelpers.respondWithJson(requests, LearnerProfileHelpers.firstPageBadges); - LearnerProfileHelpers.expectPage(learnerProfileView, LearnerProfileHelpers.firstPageBadges); - }); - - it('renders the limited profile for under 13 users', function() { - var context = createProfilePage( - true, - {year_of_birth: new Date().getFullYear() - 10, requires_parental_consent: true} - ); - var learnerProfileView = context.learnerProfileView; - LearnerProfileHelpers.expectLimitedProfileSectionsAndFieldsToBeRendered(learnerProfileView); - }); - }); - }); diff --git a/openedx/features/learner_profile/static/learner_profile/js/spec/views/badge_list_container_spec.js b/openedx/features/learner_profile/static/learner_profile/js/spec/views/badge_list_container_spec.js deleted file mode 100644 index 20342c4a6709..000000000000 --- a/openedx/features/learner_profile/static/learner_profile/js/spec/views/badge_list_container_spec.js +++ /dev/null @@ -1,99 +0,0 @@ -define([ - 'backbone', - 'jquery', - 'underscore', - 'URI', - 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', - 'edx-ui-toolkit/js/pagination/paging-collection', - 'learner_profile/js/spec_helpers/helpers', - 'learner_profile/js/views/badge_list_container' -], -function(Backbone, $, _, URI, AjaxHelpers, PagingCollection, LearnerProfileHelpers, BadgeListContainer) { - 'use strict'; - - describe('edx.user.BadgeListContainer', function() { - var view; - - var createView = function(requests, pageNum, badgeListObject) { - var BadgeCollection = PagingCollection.extend({ - queryParams: { - currentPage: 'current_page' - } - }); - var badgeCollection = new BadgeCollection(); - var models = []; - var badgeListContainer; - var request; - var path; - badgeCollection.url = '/api/badges/v1/assertions/user/staff/'; - _.each(_.range(badgeListObject.count), function(idx) { - models.push(LearnerProfileHelpers.makeBadge(idx)); - }); - badgeListObject.results = models; // eslint-disable-line no-param-reassign - badgeCollection.setPage(pageNum); - request = AjaxHelpers.currentRequest(requests); - path = new URI(request.url).path(); - expect(path).toBe('/api/badges/v1/assertions/user/staff/'); - AjaxHelpers.respondWithJson(requests, badgeListObject); - badgeListContainer = new BadgeListContainer({ - collection: badgeCollection - - }); - badgeListContainer.render(); - return badgeListContainer; - }; - - afterEach(function() { - view.$el.remove(); - }); - - it('displays all badges', function() { - var requests = AjaxHelpers.requests(this), - badges; - view = createView(requests, 1, { - count: 30, - previous: '/arbitrary/url', - num_pages: 3, - next: null, - start: 20, - current_page: 1, - results: [] - }); - badges = view.$el.find('div.badge-display'); - expect(badges.length).toBe(30); - }); - - it('displays placeholder on last page', function() { - var requests = AjaxHelpers.requests(this), - placeholder; - view = createView(requests, 3, { - count: 30, - previous: '/arbitrary/url', - num_pages: 3, - next: null, - start: 20, - current_page: 3, - results: [] - }); - placeholder = view.$el.find('span.accomplishment-placeholder'); - expect(placeholder.length).toBe(1); - }); - - it('does not display placeholder on first page', function() { - var requests = AjaxHelpers.requests(this), - placeholder; - view = createView(requests, 1, { - count: 30, - previous: '/arbitrary/url', - num_pages: 3, - next: null, - start: 0, - current_page: 1, - results: [] - }); - placeholder = view.$el.find('span.accomplishment-placeholder'); - expect(placeholder.length).toBe(0); - }); - }); -} -); diff --git a/openedx/features/learner_profile/static/learner_profile/js/spec/views/badge_list_view_spec.js b/openedx/features/learner_profile/static/learner_profile/js/spec/views/badge_list_view_spec.js deleted file mode 100644 index e8cfd32d4c38..000000000000 --- a/openedx/features/learner_profile/static/learner_profile/js/spec/views/badge_list_view_spec.js +++ /dev/null @@ -1,81 +0,0 @@ -define([ - 'backbone', - 'jquery', - 'underscore', - 'edx-ui-toolkit/js/pagination/paging-collection', - 'learner_profile/js/spec_helpers/helpers', - 'learner_profile/js/views/badge_list_view' -], -function(Backbone, $, _, PagingCollection, LearnerProfileHelpers, BadgeListView) { - 'use strict'; - - describe('edx.user.BadgeListView', function() { - var view; - - var createView = function(badges, pages, page, hasNextPage) { - var badgeCollection = new PagingCollection(); - var models = []; - var badgeList; - badgeCollection.url = '/api/badges/v1/assertions/user/staff/'; - _.each(badges, function(element) { - models.push(new Backbone.Model(element)); - }); - badgeCollection.models = models; - badgeCollection.length = badges.length; - badgeCollection.currentPage = page; - badgeCollection.totalPages = pages; - badgeCollection.hasNextPage = function() { - return hasNextPage; - }; - badgeList = new BadgeListView({ - collection: badgeCollection - - }); - return badgeList; - }; - - afterEach(function() { - view.$el.remove(); - }); - - it('there is a single row if there is only one badge', function() { - var rows; - view = createView([LearnerProfileHelpers.makeBadge(1)], 1, 1, false); - view.render(); - rows = view.$el.find('div.row'); - expect(rows.length).toBe(1); - }); - - it('accomplishments placeholder is visible on a last page', function() { - var placeholder; - view = createView([LearnerProfileHelpers.makeBadge(1)], 2, 2, false); - view.render(); - placeholder = view.$el.find('span.accomplishment-placeholder'); - expect(placeholder.length).toBe(1); - }); - - it('accomplishments placeholder to be not visible on a first page', function() { - var placeholder; - view = createView([LearnerProfileHelpers.makeBadge(1)], 1, 2, true); - view.render(); - placeholder = view.$el.find('span.accomplishment-placeholder'); - expect(placeholder.length).toBe(0); - }); - - it('badges are in two columns (checked by counting rows for a known number of badges)', function() { - var badges = []; - var placeholder; - var rows; - _.each(_.range(4), function(item) { - badges.push(LearnerProfileHelpers.makeBadge(item)); - }); - view = createView(badges, 1, 2, true); - view.render(); - placeholder = view.$el.find('span.accomplishment-placeholder'); - expect(placeholder.length).toBe(0); - rows = view.$el.find('div.row'); - expect(rows.length).toBe(2); - }); - }); -} -); diff --git a/openedx/features/learner_profile/static/learner_profile/js/spec/views/badge_view_spec.js b/openedx/features/learner_profile/static/learner_profile/js/spec/views/badge_view_spec.js deleted file mode 100644 index 8ac88ae0b17e..000000000000 --- a/openedx/features/learner_profile/static/learner_profile/js/spec/views/badge_view_spec.js +++ /dev/null @@ -1,114 +0,0 @@ -define([ - 'backbone', 'jquery', 'underscore', - 'learner_profile/js/spec_helpers/helpers', - 'learner_profile/js/views/badge_view' -], -function(Backbone, $, _, LearnerProfileHelpers, BadgeView) { - 'use strict'; - - describe('edx.user.BadgeView', function() { - var view, - badge, - testBadgeNameIsDisplayed, - testBadgeIconIsDisplayed; - - var createView = function(ownProfile) { - var options, - testView; - badge = LearnerProfileHelpers.makeBadge(1); - options = { - model: new Backbone.Model(badge), - ownProfile: ownProfile, - badgeMeta: {} - }; - testView = new BadgeView(options); - testView.render(); - $('body').append(testView.$el); - testView.$el.show(); - expect(testView.$el.is(':visible')).toBe(true); - return testView; - }; - - afterEach(function() { - view.$el.remove(); - $('.badges-modal').remove(); - }); - - it('profile of other has no share button', function() { - view = createView(false); - expect(view.context.ownProfile).toBeFalsy(); - expect(view.$el.find('button.share-button').length).toBe(0); - }); - - it('own profile has share button', function() { - view = createView(true); - expect(view.context.ownProfile).toBeTruthy(); - expect(view.$el.find('button.share-button').length).toBe(1); - }); - - it('click on share button calls createModal function', function() { - var shareButton; - view = createView(true); - spyOn(view, 'createModal'); - view.delegateEvents(); - expect(view.context.ownProfile).toBeTruthy(); - shareButton = view.$el.find('button.share-button'); - expect(shareButton.length).toBe(1); - expect(view.createModal).not.toHaveBeenCalled(); - shareButton.click(); - expect(view.createModal).toHaveBeenCalled(); - }); - - it('click on share button calls shows the dialog', function(done) { - var shareButton, - $modalElement; - view = createView(true); - expect(view.context.ownProfile).toBeTruthy(); - shareButton = view.$el.find('button.share-button'); - expect(shareButton.length).toBe(1); - $modalElement = $('.badges-modal'); - expect($modalElement.length).toBe(0); - expect($modalElement.is(':visible')).toBeFalsy(); - shareButton.click(); - // Note: this element should have appeared in the dom during: shareButton.click(); - $modalElement = $('.badges-modal'); - jasmine.waitUntil(function() { - return $modalElement.is(':visible'); - }).always(done); - }); - - testBadgeNameIsDisplayed = function(ownProfile) { - var badgeDiv; - view = createView(ownProfile); - badgeDiv = view.$el.find('.badge-name'); - expect(badgeDiv.length).toBeTruthy(); - expect(badgeDiv.is(':visible')).toBe(true); - expect(_.count(badgeDiv.html(), badge.badge_class.display_name)).toBeTruthy(); - }; - - it('test badge name is displayed for own profile', function() { - testBadgeNameIsDisplayed(true); - }); - - it('test badge name is displayed for other profile', function() { - testBadgeNameIsDisplayed(false); - }); - - testBadgeIconIsDisplayed = function(ownProfile) { - var badgeImg; - view = createView(ownProfile); - badgeImg = view.$el.find('img.badge'); - expect(badgeImg.length).toBe(1); - expect(badgeImg.attr('src')).toEqual(badge.image_url); - }; - - it('test badge icon is displayed for own profile', function() { - testBadgeIconIsDisplayed(true); - }); - - it('test badge icon is displayed for other profile', function() { - testBadgeIconIsDisplayed(false); - }); - }); -} -); diff --git a/openedx/features/learner_profile/static/learner_profile/js/spec/views/learner_profile_fields_spec.js b/openedx/features/learner_profile/static/learner_profile/js/spec/views/learner_profile_fields_spec.js deleted file mode 100644 index 49b3dbc630df..000000000000 --- a/openedx/features/learner_profile/static/learner_profile/js/spec/views/learner_profile_fields_spec.js +++ /dev/null @@ -1,381 +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/student_account/models/user_account_model', - 'learner_profile/js/views/learner_profile_fields', - 'js/views/message_banner' - ], - function(Backbone, $, _, AjaxHelpers, TemplateHelpers, Helpers, UserAccountModel, LearnerProfileFields, - MessageBannerView) { - 'use strict'; - - describe('edx.user.LearnerProfileFields', function() { - var MOCK_YEAR_OF_BIRTH = 1989; - var MOCK_IMAGE_MAX_BYTES = 64; - var MOCK_IMAGE_MIN_BYTES = 16; - - var createImageView = function(options) { - var yearOfBirth = _.isUndefined(options.yearOfBirth) ? MOCK_YEAR_OF_BIRTH : options.yearOfBirth; - var imageMaxBytes = _.isUndefined(options.imageMaxBytes) ? MOCK_IMAGE_MAX_BYTES : options.imageMaxBytes; - var imageMinBytes = _.isUndefined(options.imageMinBytes) ? MOCK_IMAGE_MIN_BYTES : options.imageMinBytes; - var messageView; - - var imageData = { - image_url_large: '/media/profile-images/default.jpg', - has_image: !!options.hasImage - }; - - var accountSettingsModel = new UserAccountModel(); - accountSettingsModel.set({profile_image: imageData}); - accountSettingsModel.set({year_of_birth: yearOfBirth}); - accountSettingsModel.set({requires_parental_consent: !!_.isEmpty(yearOfBirth)}); - - accountSettingsModel.url = Helpers.USER_ACCOUNTS_API_URL; - - messageView = new MessageBannerView({ - el: $('.message-banner') - }); - - return new LearnerProfileFields.ProfileImageFieldView({ - model: accountSettingsModel, - valueAttribute: 'profile_image', - editable: options.ownProfile, - messageView: messageView, - imageMaxBytes: imageMaxBytes, - imageMinBytes: imageMinBytes, - imageUploadUrl: Helpers.IMAGE_UPLOAD_API_URL, - imageRemoveUrl: Helpers.IMAGE_REMOVE_API_URL - }); - }; - - var createSocialLinksView = function(ownProfile, socialPlatformLinks) { - var accountSettingsModel = new UserAccountModel(); - accountSettingsModel.set({social_platforms: socialPlatformLinks}); - - return new LearnerProfileFields.SocialLinkIconsView({ - model: accountSettingsModel, - socialPlatforms: ['twitter', 'facebook', 'linkedin'], - ownProfile: ownProfile - }); - }; - - var createFakeImageFile = function(size) { - var fileFakeData = 'i63ljc6giwoskyb9x5sw0169bdcmcxr3cdz8boqv0lik971972cmd6yknvcxr5sw0nvc169bdcmcxsdf'; - return new Blob( - [fileFakeData.substr(0, size)], - {type: 'image/jpg'} - ); - }; - - var initializeUploader = function(view) { - view.$('.upload-button-input').fileupload({ - url: Helpers.IMAGE_UPLOAD_API_URL, - type: 'POST', - add: view.fileSelected, - done: view.imageChangeSucceeded, - fail: view.imageChangeFailed - }); - }; - - beforeEach(function() { - loadFixtures('learner_profile/fixtures/learner_profile.html'); - TemplateHelpers.installTemplate('templates/fields/field_image'); - TemplateHelpers.installTemplate('templates/fields/message_banner'); - TemplateHelpers.installTemplate('learner_profile/templates/social_icons'); - }); - - afterEach(function() { - // image_field.js's window.onBeforeUnload breaks Karma in Chrome, clean it up after each test - $(window).off('beforeunload'); - }); - - describe('ProfileImageFieldView', function() { - var verifyImageUploadButtonMessage = function(view, inProgress) { - var iconName = inProgress ? 'fa-spinner' : 'fa-camera'; - var message = inProgress ? view.titleUploading : view.uploadButtonTitle(); - expect(view.$('.upload-button-icon span').attr('class')).toContain(iconName); - expect(view.$('.upload-button-title').text().trim()).toBe(message); - }; - - var verifyImageRemoveButtonMessage = function(view, inProgress) { - var iconName = inProgress ? 'fa-spinner' : 'fa-remove'; - var message = inProgress ? view.titleRemoving : view.removeButtonTitle(); - expect(view.$('.remove-button-icon span').attr('class')).toContain(iconName); - expect(view.$('.remove-button-title').text().trim()).toBe(message); - }; - - it('can upload profile image', function() { - var requests = AjaxHelpers.requests(this); - var imageName = 'profile_image.jpg'; - var imageView = createImageView({ownProfile: true, hasImage: false}); - var data; - imageView.render(); - - initializeUploader(imageView); - - // Remove button should not be present for default image - expect(imageView.$('.u-field-remove-button').css('display') === 'none').toBeTruthy(); - - // For default image, image title should be `Upload an image` - verifyImageUploadButtonMessage(imageView, false); - - // Add image to upload queue. Validate the image size and send POST request to upload image - imageView.$('.upload-button-input').fileupload('add', {files: [createFakeImageFile(60)]}); - - // Verify image upload progress message - verifyImageUploadButtonMessage(imageView, true); - - // Verify if POST request received for image upload - AjaxHelpers.expectRequest(requests, 'POST', Helpers.IMAGE_UPLOAD_API_URL, new FormData()); - - // Send 204 NO CONTENT to confirm the image upload success - AjaxHelpers.respondWithNoContent(requests); - - // Upon successful image upload, account settings model will be fetched to - // get the url for newly uploaded image, So we need to send the response for that GET - data = { - profile_image: { - image_url_large: '/media/profile-images/' + imageName, - has_image: true - } - }; - AjaxHelpers.respondWithJson(requests, data); - - // Verify uploaded image name - expect(imageView.$('.image-frame').attr('src')).toContain(imageName); - - // Remove button should be present after successful image upload - expect(imageView.$('.u-field-remove-button').css('display') !== 'none').toBeTruthy(); - - // After image upload, image title should be `Change image` - verifyImageUploadButtonMessage(imageView, false); - }); - - it('can remove profile image', function() { - var requests = AjaxHelpers.requests(this); - var imageView = createImageView({ownProfile: true, hasImage: false}); - var data; - imageView.render(); - - // Verify image remove title - verifyImageRemoveButtonMessage(imageView, false); - - imageView.$('.u-field-remove-button').click(); - - // Verify image remove progress message - verifyImageRemoveButtonMessage(imageView, true); - - // Verify if POST request received for image remove - AjaxHelpers.expectRequest(requests, 'POST', Helpers.IMAGE_REMOVE_API_URL, null); - - // Send 204 NO CONTENT to confirm the image removal success - AjaxHelpers.respondWithNoContent(requests); - - // Upon successful image removal, account settings model will be fetched to get default image url - // So we need to send the response for that GET - data = { - profile_image: { - image_url_large: '/media/profile-images/default.jpg', - has_image: false - } - }; - AjaxHelpers.respondWithJson(requests, data); - - // Remove button should not be present for default image - expect(imageView.$('.u-field-remove-button').css('display') === 'none').toBeTruthy(); - }); - - it("can't remove default profile image", function() { - var imageView = createImageView({ownProfile: true, hasImage: false}); - imageView.render(); - - spyOn(imageView, 'clickedRemoveButton'); - - // Remove button should not be present for default image - expect(imageView.$('.u-field-remove-button').css('display') === 'none').toBeTruthy(); - - imageView.$('.u-field-remove-button').click(); - - // Remove button click handler should not be called - expect(imageView.clickedRemoveButton).not.toHaveBeenCalled(); - }); - - it("can't upload image having size greater than max size", function() { - var imageView = createImageView({ownProfile: true, hasImage: false}); - imageView.render(); - - initializeUploader(imageView); - - // Add image to upload queue, this will validate the image size - imageView.$('.upload-button-input').fileupload('add', {files: [createFakeImageFile(70)]}); - - // Verify error message - expect($('.message-banner').text().trim()) - .toBe('The file must be smaller than 64 bytes in size.'); - }); - - it("can't upload image having size less than min size", function() { - var imageView = createImageView({ownProfile: true, hasImage: false}); - imageView.render(); - - initializeUploader(imageView); - - // Add image to upload queue, this will validate the image size - imageView.$('.upload-button-input').fileupload('add', {files: [createFakeImageFile(10)]}); - - // Verify error message - expect($('.message-banner').text().trim()).toBe('The file must be at least 16 bytes in size.'); - }); - - it("can't upload and remove image if parental consent required", function() { - var imageView = createImageView({ownProfile: true, hasImage: false, yearOfBirth: ''}); - imageView.render(); - - spyOn(imageView, 'clickedUploadButton'); - spyOn(imageView, 'clickedRemoveButton'); - - expect(imageView.$('.u-field-upload-button').css('display') === 'none').toBeTruthy(); - expect(imageView.$('.u-field-remove-button').css('display') === 'none').toBeTruthy(); - - imageView.$('.u-field-upload-button').click(); - imageView.$('.u-field-remove-button').click(); - - expect(imageView.clickedUploadButton).not.toHaveBeenCalled(); - expect(imageView.clickedRemoveButton).not.toHaveBeenCalled(); - }); - - it("can't upload and remove image on others profile", function() { - var imageView = createImageView({ownProfile: false}); - imageView.render(); - - spyOn(imageView, 'clickedUploadButton'); - spyOn(imageView, 'clickedRemoveButton'); - - expect(imageView.$('.u-field-upload-button').css('display') === 'none').toBeTruthy(); - expect(imageView.$('.u-field-remove-button').css('display') === 'none').toBeTruthy(); - - imageView.$('.u-field-upload-button').click(); - imageView.$('.u-field-remove-button').click(); - - expect(imageView.clickedUploadButton).not.toHaveBeenCalled(); - expect(imageView.clickedRemoveButton).not.toHaveBeenCalled(); - }); - - it('shows message if we try to navigate away during image upload/remove', function() { - var imageView = createImageView({ownProfile: true, hasImage: false}); - spyOn(imageView, 'onBeforeUnload'); - imageView.render(); - - initializeUploader(imageView); - - // Add image to upload queue, this will validate image size and send POST request to upload image - imageView.$('.upload-button-input').fileupload('add', {files: [createFakeImageFile(60)]}); - - // Verify image upload progress message - verifyImageUploadButtonMessage(imageView, true); - - window.onbeforeunload = null; - $(window).trigger('beforeunload'); - expect(imageView.onBeforeUnload).toHaveBeenCalled(); - }); - - it('shows error message for HTTP 500', function() { - var requests = AjaxHelpers.requests(this); - var imageView = createImageView({ownProfile: true, hasImage: false}); - imageView.render(); - - initializeUploader(imageView); - - // Add image to upload queue. Validate the image size and send POST request to upload image - imageView.$('.upload-button-input').fileupload('add', {files: [createFakeImageFile(60)]}); - - // Verify image upload progress message - verifyImageUploadButtonMessage(imageView, true); - - // Verify if POST request received for image upload - AjaxHelpers.expectRequest(requests, 'POST', Helpers.IMAGE_UPLOAD_API_URL, new FormData()); - - // Send HTTP 500 - AjaxHelpers.respondWithError(requests); - - expect($('.message-banner').text().trim()).toBe(imageView.errorMessage); - }); - }); - - describe('SocialLinkIconsView', function() { - var socialPlatformLinks, - socialLinkData, - socialLinksView, - socialPlatform, - $icon; - - it('icons are visible and links to social profile if added in account settings', function() { - socialPlatformLinks = { - twitter: { - platform: 'twitter', - social_link: 'https://www.twitter.com/edX' - }, - facebook: { - platform: 'facebook', - social_link: 'https://www.facebook.com/edX' - }, - linkedin: { - platform: 'linkedin', - social_link: '' - } - }; - - socialLinksView = createSocialLinksView(true, socialPlatformLinks); - - // Icons should be present and contain links if defined - for (var i = 0; i < Object.keys(socialPlatformLinks); i++) { // eslint-disable-line vars-on-top - socialPlatform = Object.keys(socialPlatformLinks)[i]; - socialLinkData = socialPlatformLinks[socialPlatform]; - if (socialLinkData.social_link) { - // Icons with a social_link value should be displayed with a surrounding link - $icon = socialLinksView.$('span.fa-' + socialPlatform + '-square'); - expect($icon).toExist(); - expect($icon.parent().is('a')); - } else { - // Icons without a social_link value should be displayed without a surrounding link - $icon = socialLinksView.$('span.fa-' + socialPlatform + '-square'); - expect($icon).toExist(); - expect(!$icon.parent().is('a')); - } - } - }); - - it('icons are not visible on a profile with no links', function() { - socialPlatformLinks = { - twitter: { - platform: 'twitter', - social_link: '' - }, - facebook: { - platform: 'facebook', - social_link: '' - }, - linkedin: { - platform: 'linkedin', - social_link: '' - } - }; - - socialLinksView = createSocialLinksView(false, socialPlatformLinks); - - // Icons should not be present if not defined on another user's profile - for (var i = 0; i < Object.keys(socialPlatformLinks); i++) { // eslint-disable-line vars-on-top - socialPlatform = Object.keys(socialPlatformLinks)[i]; - socialLinkData = socialPlatformLinks[socialPlatform]; - $icon = socialLinksView.$('span.fa-' + socialPlatform + '-square'); - expect($icon).toBe(null); - } - }); - }); - }); - }); diff --git a/openedx/features/learner_profile/static/learner_profile/js/spec/views/learner_profile_view_spec.js b/openedx/features/learner_profile/static/learner_profile/js/spec/views/learner_profile_view_spec.js deleted file mode 100644 index 1797b0de05e7..000000000000 --- a/openedx/features/learner_profile/static/learner_profile/js/spec/views/learner_profile_view_spec.js +++ /dev/null @@ -1,240 +0,0 @@ -/* eslint-disable vars-on-top */ -define( - [ - 'gettext', - 'backbone', - 'jquery', - 'underscore', - 'edx-ui-toolkit/js/pagination/paging-collection', - 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', - 'common/js/spec_helpers/template_helpers', - 'js/spec/student_account/helpers', - 'learner_profile/js/spec_helpers/helpers', - 'js/views/fields', - 'js/student_account/models/user_account_model', - 'js/student_account/models/user_preferences_model', - 'learner_profile/js/views/learner_profile_fields', - 'learner_profile/js/views/learner_profile_view', - 'learner_profile/js/views/badge_list_container', - 'js/student_account/views/account_settings_fields', - 'js/views/message_banner' - ], - function(gettext, Backbone, $, _, PagingCollection, AjaxHelpers, TemplateHelpers, Helpers, LearnerProfileHelpers, - FieldViews, UserAccountModel, AccountPreferencesModel, LearnerProfileFields, LearnerProfileView, - BadgeListContainer, AccountSettingsFieldViews, MessageBannerView) { - 'use strict'; - - describe('edx.user.LearnerProfileView', function() { - var createLearnerProfileView = function(ownProfile, accountPrivacy, profileIsPublic) { - var accountSettingsModel = new UserAccountModel(); - accountSettingsModel.set(Helpers.createAccountSettingsData()); - accountSettingsModel.set({profile_is_public: profileIsPublic}); - accountSettingsModel.set({profile_image: Helpers.PROFILE_IMAGE}); - - var accountPreferencesModel = new AccountPreferencesModel(); - accountPreferencesModel.set({account_privacy: accountPrivacy}); - - accountPreferencesModel.url = Helpers.USER_PREFERENCES_API_URL; - - var editable = ownProfile ? 'toggle' : 'never'; - - var accountPrivacyFieldView = new LearnerProfileFields.AccountPrivacyFieldView({ - model: accountPreferencesModel, - required: true, - editable: 'always', - showMessages: false, - title: 'edX learners can see my:', - valueAttribute: 'account_privacy', - options: [ - ['all_users', 'Full Profile'], - ['private', 'Limited Profile'] - ], - helpMessage: '', - accountSettingsPageUrl: '/account/settings/' - }); - - var messageView = new MessageBannerView({ - el: $('.message-banner') - }); - - var profileImageFieldView = new LearnerProfileFields.ProfileImageFieldView({ - model: accountSettingsModel, - valueAttribute: 'profile_image', - editable: editable, - messageView: messageView, - imageMaxBytes: Helpers.IMAGE_MAX_BYTES, - imageMinBytes: Helpers.IMAGE_MIN_BYTES, - imageUploadUrl: Helpers.IMAGE_UPLOAD_API_URL, - imageRemoveUrl: Helpers.IMAGE_REMOVE_API_URL - }); - - var usernameFieldView = new FieldViews.ReadonlyFieldView({ - model: accountSettingsModel, - valueAttribute: 'username', - helpMessage: '' - }); - - var nameFieldView = new FieldViews.ReadonlyFieldView({ - model: accountSettingsModel, - valueAttribute: 'name', - helpMessage: '' - }); - - var sectionOneFieldViews = [ - new LearnerProfileFields.SocialLinkIconsView({ - model: accountSettingsModel, - socialPlatforms: Helpers.SOCIAL_PLATFORMS, - ownProfile: true - }), - - new FieldViews.DropdownFieldView({ - title: gettext('Location'), - model: accountSettingsModel, - required: false, - editable: editable, - showMessages: false, - placeholderValue: '', - valueAttribute: 'country', - options: Helpers.FIELD_OPTIONS, - helpMessage: '' - }), - - new AccountSettingsFieldViews.LanguageProficienciesFieldView({ - title: gettext('Language'), - model: accountSettingsModel, - required: false, - editable: editable, - showMessages: false, - placeholderValue: 'Add language', - valueAttribute: 'language_proficiencies', - options: Helpers.FIELD_OPTIONS, - helpMessage: '' - }), - - new FieldViews.DateFieldView({ - model: accountSettingsModel, - valueAttribute: 'date_joined', - helpMessage: '' - }) - ]; - - var sectionTwoFieldViews = [ - new FieldViews.TextareaFieldView({ - model: accountSettingsModel, - editable: editable, - showMessages: false, - title: 'About me', - placeholderValue: 'Tell other edX learners a little about yourself: where you live, ' - + "what your interests are, why you're taking courses on edX, or what you hope to learn.", - valueAttribute: 'bio', - helpMessage: '', - messagePosition: 'header' - }) - ]; - - var badgeCollection = new PagingCollection(); - badgeCollection.url = Helpers.BADGES_API_URL; - - var badgeListContainer = new BadgeListContainer({ - attributes: {class: 'badge-set-display'}, - collection: badgeCollection, - find_courses_url: Helpers.FIND_COURSES_URL - }); - - return new LearnerProfileView( - { - el: $('.wrapper-profile'), - ownProfile: ownProfile, - hasPreferencesAccess: true, - accountSettingsModel: accountSettingsModel, - preferencesModel: accountPreferencesModel, - accountPrivacyFieldView: accountPrivacyFieldView, - usernameFieldView: usernameFieldView, - nameFieldView: nameFieldView, - profileImageFieldView: profileImageFieldView, - sectionOneFieldViews: sectionOneFieldViews, - sectionTwoFieldViews: sectionTwoFieldViews, - badgeListContainer: badgeListContainer - }); - }; - - beforeEach(function() { - loadFixtures('learner_profile/fixtures/learner_profile.html'); - }); - - afterEach(function() { - Backbone.history.stop(); - }); - - it('shows loading error correctly', function() { - var learnerProfileView = createLearnerProfileView(false, 'all_users'); - - Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, true); - Helpers.expectLoadingErrorIsVisible(learnerProfileView, false); - - learnerProfileView.render(); - learnerProfileView.showLoadingError(); - - Helpers.expectLoadingErrorIsVisible(learnerProfileView, true); - }); - - it('renders all fields as expected for self with full access', function() { - var learnerProfileView = createLearnerProfileView(true, 'all_users', true); - - Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, true); - Helpers.expectLoadingErrorIsVisible(learnerProfileView, false); - - learnerProfileView.render(); - - Helpers.expectLoadingErrorIsVisible(learnerProfileView, false); - LearnerProfileHelpers.expectProfileSectionsAndFieldsToBeRendered(learnerProfileView); - }); - - it('renders all fields as expected for self with limited access', function() { - var learnerProfileView = createLearnerProfileView(true, 'private', false); - - Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, true); - Helpers.expectLoadingErrorIsVisible(learnerProfileView, false); - - learnerProfileView.render(); - - Helpers.expectLoadingErrorIsVisible(learnerProfileView, false); - LearnerProfileHelpers.expectLimitedProfileSectionsAndFieldsToBeRendered(learnerProfileView); - }); - - it('renders the fields as expected for others with full access', function() { - var learnerProfileView = createLearnerProfileView(false, 'all_users', true); - - Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, true); - Helpers.expectLoadingErrorIsVisible(learnerProfileView, false); - - learnerProfileView.render(); - - Helpers.expectLoadingErrorIsVisible(learnerProfileView, false); - LearnerProfileHelpers.expectProfileSectionsAndFieldsToBeRendered(learnerProfileView, true); - }); - - it('renders the fields as expected for others with limited access', function() { - var learnerProfileView = createLearnerProfileView(false, 'private', false); - - Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, true); - Helpers.expectLoadingErrorIsVisible(learnerProfileView, false); - - learnerProfileView.render(); - - Helpers.expectLoadingErrorIsVisible(learnerProfileView, false); - LearnerProfileHelpers.expectLimitedProfileSectionsAndFieldsToBeRendered(learnerProfileView, true); - }); - - it("renders an error if the badges can't be fetched", function() { - var learnerProfileView = createLearnerProfileView(false, 'all_users', true); - learnerProfileView.options.accountSettingsModel.set({accomplishments_shared: true}); - var requests = AjaxHelpers.requests(this); - - learnerProfileView.render(); - - LearnerProfileHelpers.breakBadgeLoading(learnerProfileView, requests); - LearnerProfileHelpers.expectBadgeLoadingErrorIsRendered(learnerProfileView); - }); - }); - }); diff --git a/openedx/features/learner_profile/static/learner_profile/js/spec/views/section_two_tab_spec.js b/openedx/features/learner_profile/static/learner_profile/js/spec/views/section_two_tab_spec.js deleted file mode 100644 index d0e22d670beb..000000000000 --- a/openedx/features/learner_profile/static/learner_profile/js/spec/views/section_two_tab_spec.js +++ /dev/null @@ -1,113 +0,0 @@ -/* eslint-disable vars-on-top */ -define( - [ - 'backbone', 'jquery', 'underscore', - 'js/spec/student_account/helpers', - 'learner_profile/js/views/section_two_tab', - 'js/views/fields', - 'js/student_account/models/user_account_model' - ], - function(Backbone, $, _, Helpers, SectionTwoTabView, FieldViews, UserAccountModel) { - 'use strict'; - - describe('edx.user.SectionTwoTab', function() { - var createSectionTwoView = function(ownProfile, profileIsPublic) { - var accountSettingsModel = new UserAccountModel(); - accountSettingsModel.set(Helpers.createAccountSettingsData()); - accountSettingsModel.set({profile_is_public: profileIsPublic}); - accountSettingsModel.set({profile_image: Helpers.PROFILE_IMAGE}); - - var editable = ownProfile ? 'toggle' : 'never'; - - var sectionTwoFieldViews = [ - new FieldViews.TextareaFieldView({ - model: accountSettingsModel, - editable: editable, - showMessages: false, - title: 'About me', - placeholderValue: 'Tell other edX learners a little about yourself: where you live, ' - + "what your interests are, why you're taking courses on edX, or what you hope to learn.", - valueAttribute: 'bio', - helpMessage: '', - messagePosition: 'header' - }) - ]; - - return new SectionTwoTabView({ - viewList: sectionTwoFieldViews, - showFullProfile: function() { - return profileIsPublic; - }, - ownProfile: ownProfile - }); - }; - - it('full profile displayed for public profile', function() { - var view = createSectionTwoView(false, true); - view.render(); - var bio = view.$el.find('.u-field-bio'); - expect(bio.length).toBe(1); - }); - - it('profile field parts are actually rendered for public profile', function() { - var view = createSectionTwoView(false, true); - _.each(view.options.viewList, function(fieldView) { - spyOn(fieldView, 'render').and.callThrough(); - }); - view.render(); - _.each(view.options.viewList, function(fieldView) { - expect(fieldView.render).toHaveBeenCalled(); - }); - }); - - var testPrivateProfile = function(ownProfile, messageString) { - var view = createSectionTwoView(ownProfile, false); - view.render(); - var bio = view.$el.find('.u-field-bio'); - expect(bio.length).toBe(0); - var msg = view.$el.find('span.profile-private-message'); - expect(msg.length).toBe(1); - expect(_.count(msg.html(), messageString)).toBeTruthy(); - }; - - it('no profile when profile is private for other people', function() { - testPrivateProfile(false, 'This learner is currently sharing a limited profile'); - }); - - it('no profile when profile is private for the user herself', function() { - testPrivateProfile(true, 'You are currently sharing a limited profile'); - }); - - var testProfilePrivatePartsDoNotRender = function(ownProfile) { - var view = createSectionTwoView(ownProfile, false); - _.each(view.options.viewList, function(fieldView) { - spyOn(fieldView, 'render'); - }); - view.render(); - _.each(view.options.viewList, function(fieldView) { - expect(fieldView.render).not.toHaveBeenCalled(); - }); - }; - - it('profile field parts are not rendered for private profile for owner', function() { - testProfilePrivatePartsDoNotRender(true); - }); - - it('profile field parts are not rendered for private profile for other people', function() { - testProfilePrivatePartsDoNotRender(false); - }); - - it('does not allow fields to be edited when visiting a profile for other people', function() { - var view = createSectionTwoView(false, true); - var bio = view.options.viewList[0]; - expect(bio.editable).toBe('never'); - }); - - it("allows fields to be edited when visiting one's own profile", function() { - var view = createSectionTwoView(true, true); - var bio = view.options.viewList[0]; - expect(bio.editable).toBe('toggle'); - }); - }); - } -); diff --git a/openedx/features/learner_profile/static/learner_profile/js/spec/views/share_modal_view_spec.js b/openedx/features/learner_profile/static/learner_profile/js/spec/views/share_modal_view_spec.js deleted file mode 100644 index e3d15659ffa6..000000000000 --- a/openedx/features/learner_profile/static/learner_profile/js/spec/views/share_modal_view_spec.js +++ /dev/null @@ -1,63 +0,0 @@ -define( - [ - 'backbone', 'jquery', 'underscore', 'moment', - 'js/spec/student_account/helpers', - 'learner_profile/js/spec_helpers/helpers', - 'learner_profile/js/views/share_modal_view', - 'jquery.simulate' - ], - function(Backbone, $, _, Moment, Helpers, LearnerProfileHelpers, ShareModalView) { - 'use strict'; - - describe('edx.user.ShareModalView', function() { - var keys = $.simulate.keyCode; - - var view; - - var createModalView = function() { - var badge = LearnerProfileHelpers.makeBadge(1); - var context = _.extend(badge, { - created: new Moment(badge.created), - ownProfile: true, - badgeMeta: {} - }); - return new ShareModalView({ - model: new Backbone.Model(context), - shareButton: $(' -

<%- gettext("Share on Mozilla Backpack") %>

-

<%- gettext("To share your certificate on Mozilla Backpack, you must first have a Backpack account. Complete the following steps to add your certificate to Backpack.") %> -

- - -
    -
  1. - <%= edx.HtmlUtils.interpolateHtml( - gettext("Create a {link_start}Mozilla Backpack{link_end} account, or log in to your existing account"), - { - link_start: edx.HtmlUtils.HTML(''), - link_end: edx.HtmlUtils.HTML('') - } - ) - %> -
  2. - -
  3. - <%= edx.HtmlUtils.interpolateHtml( - gettext("{download_link_start}Download this image (right-click or option-click, save as){link_end} and then {upload_link_start}upload{link_end} it to your backpack."), - { - download_link_start: edx.HtmlUtils.joinHtml( - edx.HtmlUtils.HTML(''), - ), - link_end: edx.HtmlUtils.HTML(''), - upload_link_start: edx.HtmlUtils.HTML('') - } - ) - %> -
  4. -
-
- -
- -
\ No newline at end of file diff --git a/openedx/features/learner_profile/static/learner_profile/templates/social_icons.underscore b/openedx/features/learner_profile/static/learner_profile/templates/social_icons.underscore deleted file mode 100644 index 52b864cfb669..000000000000 --- a/openedx/features/learner_profile/static/learner_profile/templates/social_icons.underscore +++ /dev/null @@ -1,9 +0,0 @@ -
diff --git a/openedx/features/learner_profile/static/learner_profile/templates/third_party_auth.html b/openedx/features/learner_profile/static/learner_profile/templates/third_party_auth.html deleted file mode 100644 index 07e14bc48ab5..000000000000 --- a/openedx/features/learner_profile/static/learner_profile/templates/third_party_auth.html +++ /dev/null @@ -1,47 +0,0 @@ -<%page expression_filter="h"/> -<%! -from django.utils.translation import gettext as _ -from common.djangoapps.third_party_auth import pipeline -%> - -
  • - - ## 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: -
    -
    - % if state.has_account: - ${_('Linked')} - % else: - ${_('Not Linked')} - % endif -
    - ${state.provider.name} - -
    - % if state.has_account: - - - - % elif state.provider.display_for_login: - - ## Translators: clicking on this creates a link between a user's edX account and their account with an external authentication provider (like Google or LinkedIn). - ${_("Link")} - - % endif -
    -
    -
    - % endfor -
    -
  • diff --git a/openedx/features/learner_profile/templates/learner_profile/learner-achievements-fragment.html b/openedx/features/learner_profile/templates/learner_profile/learner-achievements-fragment.html deleted file mode 100644 index 09e6ce36b9b3..000000000000 --- a/openedx/features/learner_profile/templates/learner_profile/learner-achievements-fragment.html +++ /dev/null @@ -1,69 +0,0 @@ -## mako - -<%page expression_filter="h"/> - -<%namespace name='static' file='/static_content.html'/> - -<%! -from django.utils.translation import gettext as _ -from openedx.core.djangolib.markup import HTML, Text -%> - -
    - % if course_certificates or own_profile: -

    Course Certificates

    - % if course_certificates: - % for certificate in course_certificates: - <% - course = certificate['course'] - - completion_date_message_html = Text(_('Completed {completion_date_html}')).format( - completion_date_html=HTML( - '' - ).format( - completion_date=certificate['created'], - user_timezone=user_timezone, - user_language=user_language, - ), - ) - %> -
    - -
    -
    ${course.display_org_with_default}
    -
    ${course.display_name_with_default}
    -

    ${completion_date_message_html}

    -
    -
    - % endfor - % elif own_profile: -
    -

    ${_("You haven't earned any certificates yet.")}

    - % if settings.FEATURES.get('COURSES_ARE_BROWSABLE'): -

    - - - ${_('Explore New Courses')} - -

    - % endif -
    - % endif - % endif -
    - -<%static:require_module_async module_name="js/dateutil_factory" class_name="DateUtilFactory"> - DateUtilFactory.transform('.localized-datetime'); - diff --git a/openedx/features/learner_profile/templates/learner_profile/learner_profile.html b/openedx/features/learner_profile/templates/learner_profile/learner_profile.html deleted file mode 100644 index 6de4744e66f7..000000000000 --- a/openedx/features/learner_profile/templates/learner_profile/learner_profile.html +++ /dev/null @@ -1,79 +0,0 @@ -## mako - -<%page expression_filter="h"/> -<%inherit file="/main.html" /> -<%def name="online_help_token()"><% return "profile" %> -<%namespace name='static' file='/static_content.html'/> - -<%! -import json -from django.urls import reverse -from django.utils.translation import gettext as _ -from openedx.core.djangolib.js_utils import dump_js_escaped_json -from openedx.core.djangolib.markup import HTML -%> - -<%block name="pagetitle">${_("Learner Profile")} - -<%block name="bodyclass">view-profile - -<%block name="headextra"> -<%static:css group='style-course'/> - - -
    -
    -
    -
    - - % if own_profile: -
    -

    ${_("My Profile")}

    -
    - ${_('Build out your profile to personalize your identity on {platform_name}.').format( - platform_name=platform_name, - )} -
    -
    - % endif - -
    -
    -
    - -<%block name="js_extra"> -<%static:require_module module_name="learner_profile/js/learner_profile_factory" class_name="LearnerProfileFactory"> - var options = ${data | n, dump_js_escaped_json}; - LearnerProfileFactory(options); - - diff --git a/openedx/features/learner_profile/tests/__init__.py b/openedx/features/learner_profile/tests/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/openedx/features/learner_profile/tests/views/__init__.py b/openedx/features/learner_profile/tests/views/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/openedx/features/learner_profile/tests/views/test_learner_profile.py b/openedx/features/learner_profile/tests/views/test_learner_profile.py deleted file mode 100644 index c4c83520008b..000000000000 --- a/openedx/features/learner_profile/tests/views/test_learner_profile.py +++ /dev/null @@ -1,281 +0,0 @@ -""" Tests for student profile views. """ - - -import datetime -from unittest import mock - -import ddt -from django.conf import settings -from django.test.client import RequestFactory -from django.urls import reverse -from edx_toggles.toggles.testutils import override_waffle_flag -from opaque_keys.edx.locator import CourseLocator - -from common.djangoapps.course_modes.models import CourseMode -from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory -from common.djangoapps.util.testing import UrlResetMixin -from lms.djangoapps.certificates.data import CertificateStatuses -from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFactory -from lms.envs.test import CREDENTIALS_PUBLIC_SERVICE_URL -from openedx.core.djangoapps.content.course_overviews.models import CourseOverview -from openedx.core.djangoapps.site_configuration.tests.mixins import SiteMixin -from openedx.features.learner_profile.toggles import REDIRECT_TO_PROFILE_MICROFRONTEND -from openedx.features.learner_profile.views.learner_profile import learner_profile_context -from xmodule.data import CertificatesDisplayBehaviors # lint-amnesty, pylint: disable=wrong-import-order -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order -from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order - - -@ddt.ddt -class LearnerProfileViewTest(SiteMixin, UrlResetMixin, ModuleStoreTestCase): - """ Tests for the student profile view. """ - - USERNAME = "username" - OTHER_USERNAME = "other_user" - PASSWORD = "password" - DOWNLOAD_URL = "http://www.example.com/certificate.pdf" - CONTEXT_DATA = [ - 'default_public_account_fields', - 'accounts_api_url', - 'preferences_api_url', - 'account_settings_page_url', - 'has_preferences_access', - 'own_profile', - 'country_options', - 'language_options', - 'account_settings_data', - 'preferences_data', - ] - - def setUp(self): - super().setUp() - self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD) - self.other_user = UserFactory.create(username=self.OTHER_USERNAME, password=self.PASSWORD) - self.client.login(username=self.USERNAME, password=self.PASSWORD) - self.course = CourseFactory.create( - start=datetime.datetime(2013, 9, 16, 7, 17, 28), - end=datetime.datetime.now(), - certificate_available_date=datetime.datetime.now(), - ) - - def test_context(self): - """ - Verify learner profile page context data. - """ - request = RequestFactory().get('/url') - request.user = self.user - - context = learner_profile_context(request, self.USERNAME, self.user.is_staff) - - assert context['data']['default_public_account_fields'] == \ - settings.ACCOUNT_VISIBILITY_CONFIGURATION['public_fields'] - - assert context['data']['accounts_api_url'] == \ - reverse('accounts_api', kwargs={'username': self.user.username}) - - assert context['data']['preferences_api_url'] == \ - reverse('preferences_api', kwargs={'username': self.user.username}) - - assert context['data']['profile_image_upload_url'] == \ - reverse('profile_image_upload', kwargs={'username': self.user.username}) - - assert context['data']['profile_image_remove_url'] == \ - reverse('profile_image_remove', kwargs={'username': self.user.username}) - - assert context['data']['profile_image_max_bytes'] == settings.PROFILE_IMAGE_MAX_BYTES - - assert context['data']['profile_image_min_bytes'] == settings.PROFILE_IMAGE_MIN_BYTES - - assert context['data']['account_settings_page_url'] == reverse('account_settings') - - for attribute in self.CONTEXT_DATA: - assert attribute in context['data'] - - def test_view(self): - """ - Verify learner profile page view. - """ - profile_path = reverse('learner_profile', kwargs={'username': self.USERNAME}) - response = self.client.get(path=profile_path) - - for attribute in self.CONTEXT_DATA: - self.assertContains(response, attribute) - - def test_redirect_view(self): - with override_waffle_flag(REDIRECT_TO_PROFILE_MICROFRONTEND, active=True): - profile_path = reverse('learner_profile', kwargs={'username': self.USERNAME}) - - # Test with waffle flag active and site setting disabled, does not redirect - response = self.client.get(path=profile_path) - for attribute in self.CONTEXT_DATA: - self.assertContains(response, attribute) - - # Test with waffle flag active and site setting enabled, redirects to microfrontend - site_domain = 'othersite.example.com' - self.set_up_site(site_domain, { - 'SITE_NAME': site_domain, - 'ENABLE_PROFILE_MICROFRONTEND': True - }) - self.client.login(username=self.USERNAME, password=self.PASSWORD) - response = self.client.get(path=profile_path) - profile_url = settings.PROFILE_MICROFRONTEND_URL - self.assertRedirects(response, profile_url + self.USERNAME, fetch_redirect_response=False) - - def test_records_link(self): - profile_path = reverse('learner_profile', kwargs={'username': self.USERNAME}) - response = self.client.get(path=profile_path) - self.assertContains(response, f'') - - def test_undefined_profile_page(self): - """ - Verify that a 404 is returned for a non-existent profile page. - """ - profile_path = reverse('learner_profile', kwargs={'username': "no_such_user"}) - response = self.client.get(path=profile_path) - assert 404 == response.status_code - - def _create_certificate(self, course_key=None, enrollment_mode=CourseMode.HONOR, status='downloadable'): - """Simulate that the user has a generated certificate. """ - CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id, mode=enrollment_mode) - return GeneratedCertificateFactory( - user=self.user, - course_id=course_key or self.course.id, - mode=enrollment_mode, - download_url=self.DOWNLOAD_URL, - status=status, - ) - - @ddt.data(CourseMode.HONOR, CourseMode.PROFESSIONAL, CourseMode.VERIFIED) - def test_certificate_visibility(self, cert_mode): - """ - Verify that certificates are displayed with the correct card mode. - """ - # Add new certificate - cert = self._create_certificate(enrollment_mode=cert_mode) - cert.save() - - response = self.client.get(f'/u/{self.user.username}') - - self.assertContains(response, f'card certificate-card mode-{cert_mode}') - - @ddt.data( - ['downloadable', True], - ['notpassing', False], - ) - @ddt.unpack - def test_certificate_status_visibility(self, status, is_passed_status): - """ - Verify that certificates are only displayed for passing status. - """ - # Add new certificate - cert = self._create_certificate(status=status) - cert.save() - - # Ensure that this test is actually using both passing and non-passing certs. - assert CertificateStatuses.is_passing_status(cert.status) == is_passed_status - - response = self.client.get(f'/u/{self.user.username}') - - if is_passed_status: - self.assertContains(response, f'card certificate-card mode-{cert.mode}') - else: - self.assertNotContains(response, f'card certificate-card mode-{cert.mode}') - - def test_certificate_for_missing_course(self): - """ - Verify that a certificate is not shown for a missing course. - """ - # Add new certificate - cert = self._create_certificate(course_key=CourseLocator.from_string('course-v1:edX+INVALID+1')) - cert.save() - - response = self.client.get(f'/u/{self.user.username}') - - self.assertNotContains(response, f'card certificate-card mode-{cert.mode}') - - @ddt.data(True, False) - def test_no_certificate_visibility(self, own_profile): - """ - Verify that the 'You haven't earned any certificates yet.' well appears on the user's - own profile when they do not have certificates and does not appear when viewing - another user that does not have any certificates. - """ - profile_username = self.user.username if own_profile else self.other_user.username - response = self.client.get(f'/u/{profile_username}') - - if own_profile: - self.assertContains(response, 'You haven't earned any certificates yet.') - else: - self.assertNotContains(response, 'You haven't earned any certificates yet.') - - @ddt.data(True, False) - def test_explore_courses_visibility(self, courses_browsable): - with mock.patch.dict('django.conf.settings.FEATURES', {'COURSES_ARE_BROWSABLE': courses_browsable}): - response = self.client.get(f'/u/{self.user.username}') - if courses_browsable: - self.assertContains(response, 'Explore New Courses') - else: - self.assertNotContains(response, 'Explore New Courses') - - def test_certificate_for_visibility_for_not_viewable_course(self): - """ - Verify that a certificate is not shown if certificate are not viewable to users. - """ - # add new course with certificate_available_date is future date. - course = CourseFactory.create( - certificate_available_date=datetime.datetime.now() + datetime.timedelta(days=5), - certificates_display_behavior=CertificatesDisplayBehaviors.END_WITH_DATE - ) - - cert = self._create_certificate(course_key=course.id) - cert.save() - - response = self.client.get(f'/u/{self.user.username}') - - self.assertNotContains(response, f'card certificate-card mode-{cert.mode}') - - def test_certificates_visible_only_for_staff_and_profile_user(self): - """ - Verify that certificates data are passed to template only in case of staff user - and profile user. - """ - request = RequestFactory().get('/url') - request.user = self.user - profile_username = self.other_user.username - user_is_staff = True - context = learner_profile_context(request, profile_username, user_is_staff) - - assert 'achievements_fragment' in context - - user_is_staff = False - context = learner_profile_context(request, profile_username, user_is_staff) - assert 'achievements_fragment' not in context - - profile_username = self.user.username - context = learner_profile_context(request, profile_username, user_is_staff) - assert 'achievements_fragment' in context - - @mock.patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': True}) - def test_certificate_visibility_with_no_cert_config(self): - """ - Verify that certificates are not displayed until there is an active - certificate configuration. - """ - # Add new certificate - cert = self._create_certificate(enrollment_mode=CourseMode.VERIFIED) - cert.download_url = '' - cert.save() - - response = self.client.get(f'/u/{self.user.username}') - self.assertNotContains( - response, f'card certificate-card mode-{CourseMode.VERIFIED}' - ) - - course_overview = CourseOverview.get_from_id(self.course.id) - course_overview.has_any_active_web_certificate = True - course_overview.save() - - response = self.client.get(f'/u/{self.user.username}') - self.assertContains( - response, f'card certificate-card mode-{CourseMode.VERIFIED}' - ) diff --git a/openedx/features/learner_profile/toggles.py b/openedx/features/learner_profile/toggles.py deleted file mode 100644 index 08378b6e9042..000000000000 --- a/openedx/features/learner_profile/toggles.py +++ /dev/null @@ -1,29 +0,0 @@ -""" -Toggles for Learner Profile page. -""" - - -from edx_toggles.toggles import WaffleFlag -from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers - -# Namespace for learner profile waffle flags. -WAFFLE_FLAG_NAMESPACE = 'learner_profile' - -# Waffle flag to redirect to another learner profile experience. -# .. toggle_name: learner_profile.redirect_to_microfrontend -# .. toggle_implementation: WaffleFlag -# .. toggle_default: False -# .. toggle_description: Supports staged rollout of a new micro-frontend-based implementation of the profile page. -# .. toggle_use_cases: temporary, open_edx -# .. toggle_creation_date: 2019-02-19 -# .. toggle_target_removal_date: 2020-12-31 -# .. toggle_warning: Also set settings.PROFILE_MICROFRONTEND_URL and site's ENABLE_PROFILE_MICROFRONTEND. -# .. toggle_tickets: DEPR-17 -REDIRECT_TO_PROFILE_MICROFRONTEND = WaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.redirect_to_microfrontend', __name__) - - -def should_redirect_to_profile_microfrontend(): - return ( - configuration_helpers.get_value('ENABLE_PROFILE_MICROFRONTEND') and - REDIRECT_TO_PROFILE_MICROFRONTEND.is_enabled() - ) diff --git a/openedx/features/learner_profile/urls.py b/openedx/features/learner_profile/urls.py deleted file mode 100644 index 0f020765686b..000000000000 --- a/openedx/features/learner_profile/urls.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -Defines URLs for the learner profile. -""" - - -from django.conf import settings -from django.urls import path, re_path - -from openedx.features.learner_profile.views.learner_profile import learner_profile - -from .views.learner_achievements import LearnerAchievementsFragmentView - -urlpatterns = [ - re_path( - r'^{username_pattern}$'.format( - username_pattern=settings.USERNAME_PATTERN, - ), - learner_profile, - name='learner_profile', - ), - path('achievements', LearnerAchievementsFragmentView.as_view(), - name='openedx.learner_profile.learner_achievements_fragment_view', - ), -] diff --git a/openedx/features/learner_profile/views/__init__.py b/openedx/features/learner_profile/views/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/openedx/features/learner_profile/views/learner_achievements.py b/openedx/features/learner_profile/views/learner_achievements.py deleted file mode 100644 index 6a7a07e3392d..000000000000 --- a/openedx/features/learner_profile/views/learner_achievements.py +++ /dev/null @@ -1,58 +0,0 @@ -""" -Views to render a learner's achievements. -""" - - -from django.template.loader import render_to_string -from web_fragments.fragment import Fragment - -from lms.djangoapps.certificates import api as certificate_api -from openedx.core.djangoapps.content.course_overviews.models import CourseOverview -from openedx.core.djangoapps.plugin_api.views import EdxFragmentView - - -class LearnerAchievementsFragmentView(EdxFragmentView): - """ - A fragment to render a learner's achievements. - """ - - def render_to_fragment(self, request, username=None, own_profile=False, **kwargs): # lint-amnesty, pylint: disable=arguments-differ - """ - Renders the current learner's achievements. - """ - course_certificates = self._get_ordered_certificates_for_user(request, username) - context = { - 'course_certificates': course_certificates, - 'own_profile': own_profile, - 'disable_courseware_js': True, - } - if course_certificates or own_profile: - html = render_to_string('learner_profile/learner-achievements-fragment.html', context) - return Fragment(html) - else: - return None - - def _get_ordered_certificates_for_user(self, request, username): - """ - Returns a user's certificates sorted by course name. - """ - course_certificates = certificate_api.get_certificates_for_user(username) - passing_certificates = [] - for course_certificate in course_certificates: - if course_certificate.get('is_passing', False): - course_key = course_certificate['course_key'] - try: - course_overview = CourseOverview.get_from_id(course_key) - course_certificate['course'] = course_overview - if certificate_api.certificates_viewable_for_course(course_overview): - # add certificate into passing certificate list only if it's a PDF certificate - # or there is an active certificate configuration. - if course_certificate['is_pdf_certificate'] or course_overview.has_any_active_web_certificate: - passing_certificates.append(course_certificate) - except CourseOverview.DoesNotExist: - # This is unlikely to fail as the course should exist. - # Ideally the cert should have all the information that - # it needs. This might be solved by the Credentials API. - pass - passing_certificates.sort(key=lambda certificate: certificate['course'].display_name_with_default) - return passing_certificates diff --git a/openedx/features/learner_profile/views/learner_profile.py b/openedx/features/learner_profile/views/learner_profile.py deleted file mode 100644 index e19e6853e8eb..000000000000 --- a/openedx/features/learner_profile/views/learner_profile.py +++ /dev/null @@ -1,134 +0,0 @@ -""" Views for a student's profile information. """ - - -from django.conf import settings -from django.contrib.auth.decorators import login_required -from django.contrib.staticfiles.storage import staticfiles_storage -from django.core.exceptions import ObjectDoesNotExist -from django.http import Http404 -from django.shortcuts import redirect, render -from django.urls import reverse -from django.views.decorators.http import require_http_methods -from django_countries import countries - -from lms.djangoapps.badges.utils import badges_enabled -from common.djangoapps.edxmako.shortcuts import marketing_link -from openedx.core.djangoapps.credentials.utils import get_credentials_records_url -from openedx.core.djangoapps.programs.models import ProgramsApiConfig -from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers -from openedx.core.djangoapps.user_api.accounts.api import get_account_settings -from openedx.core.djangoapps.user_api.errors import UserNotAuthorized, UserNotFound -from openedx.core.djangoapps.user_api.preferences.api import get_user_preferences -from openedx.features.learner_profile.toggles import should_redirect_to_profile_microfrontend -from openedx.features.learner_profile.views.learner_achievements import LearnerAchievementsFragmentView -from common.djangoapps.student.models import User - - -@login_required -@require_http_methods(['GET']) -def learner_profile(request, username): - """Render the profile page for the specified username. - - Args: - request (HttpRequest) - username (str): username of user whose profile is requested. - - Returns: - HttpResponse: 200 if the page was sent successfully - HttpResponse: 302 if not logged in (redirect to login page) - HttpResponse: 405 if using an unsupported HTTP method - Raises: - Http404: 404 if the specified user is not authorized or does not exist - - Example usage: - GET /account/profile - """ - if should_redirect_to_profile_microfrontend(): - profile_microfrontend_url = f"{settings.PROFILE_MICROFRONTEND_URL}{username}" - if request.GET: - profile_microfrontend_url += f'?{request.GET.urlencode()}' - return redirect(profile_microfrontend_url) - - try: - context = learner_profile_context(request, username, request.user.is_staff) - return render( - request=request, - template_name='learner_profile/learner_profile.html', - context=context - ) - except (UserNotAuthorized, UserNotFound, ObjectDoesNotExist): - raise Http404 # lint-amnesty, pylint: disable=raise-missing-from - - -def learner_profile_context(request, profile_username, user_is_staff): - """Context for the learner profile page. - - Args: - logged_in_user (object): Logged In user. - profile_username (str): username of user whose profile is requested. - user_is_staff (bool): Logged In user has staff access. - build_absolute_uri_func (): - - Returns: - dict - - Raises: - ObjectDoesNotExist: the specified profile_username does not exist. - """ - profile_user = User.objects.get(username=profile_username) - logged_in_user = request.user - - own_profile = (logged_in_user.username == profile_username) - - account_settings_data = get_account_settings(request, [profile_username])[0] - - preferences_data = get_user_preferences(profile_user, profile_username) - - context = { - 'own_profile': own_profile, - 'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME), - 'data': { - 'profile_user_id': profile_user.id, - 'default_public_account_fields': settings.ACCOUNT_VISIBILITY_CONFIGURATION['public_fields'], - 'default_visibility': settings.ACCOUNT_VISIBILITY_CONFIGURATION['default_visibility'], - 'accounts_api_url': reverse("accounts_api", kwargs={'username': profile_username}), - 'preferences_api_url': reverse('preferences_api', kwargs={'username': profile_username}), - 'preferences_data': preferences_data, - 'account_settings_data': account_settings_data, - 'profile_image_upload_url': reverse('profile_image_upload', kwargs={'username': profile_username}), - 'profile_image_remove_url': reverse('profile_image_remove', kwargs={'username': profile_username}), - 'profile_image_max_bytes': settings.PROFILE_IMAGE_MAX_BYTES, - 'profile_image_min_bytes': settings.PROFILE_IMAGE_MIN_BYTES, - 'account_settings_page_url': reverse('account_settings'), - 'has_preferences_access': (logged_in_user.username == profile_username or user_is_staff), - 'own_profile': own_profile, - 'country_options': list(countries), - 'find_courses_url': marketing_link('COURSES'), - 'language_options': settings.ALL_LANGUAGES, - 'badges_logo': staticfiles_storage.url('certificates/images/backpack-logo.png'), - 'badges_icon': staticfiles_storage.url('certificates/images/ico-mozillaopenbadges.png'), - 'backpack_ui_img': staticfiles_storage.url('certificates/images/backpack-ui.png'), - 'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME), - 'social_platforms': settings.SOCIAL_PLATFORMS, - 'enable_coppa_compliance': settings.ENABLE_COPPA_COMPLIANCE, - 'parental_consent_age_limit': settings.PARENTAL_CONSENT_AGE_LIMIT - }, - 'show_program_listing': ProgramsApiConfig.is_enabled(), - 'show_dashboard_tabs': True, - 'disable_courseware_js': True, - 'nav_hidden': True, - 'records_url': get_credentials_records_url(), - } - - if own_profile or user_is_staff: - achievements_fragment = LearnerAchievementsFragmentView().render_to_fragment( - request, - username=profile_user.username, - own_profile=own_profile, - ) - context['achievements_fragment'] = achievements_fragment - - if badges_enabled(): - context['data']['badges_api_url'] = reverse("badges_api:user_assertions", kwargs={'username': profile_username}) - - return context diff --git a/webpack-config/file-lists.js b/webpack-config/file-lists.js index ddb9cf4f7806..67bd319c28ef 100644 --- a/webpack-config/file-lists.js +++ b/webpack-config/file-lists.js @@ -79,9 +79,6 @@ module.exports = { path.resolve(__dirname, '../lms/static/js/learner_dashboard/views/program_header_view.js'), path.resolve(__dirname, '../lms/static/js/learner_dashboard/views/sidebar_view.js'), path.resolve(__dirname, '../lms/static/js/learner_dashboard/views/upgrade_message_view.js'), - path.resolve(__dirname, '../lms/static/js/student_account/views/account_section_view.js'), - path.resolve(__dirname, '../lms/static/js/student_account/views/account_settings_fields.js'), - path.resolve(__dirname, '../lms/static/js/student_account/views/account_settings_view.js'), path.resolve(__dirname, '../lms/static/js/student_account/views/FormView.js'), path.resolve(__dirname, '../lms/static/js/student_account/views/LoginView.js'), path.resolve(__dirname, '../lms/static/js/student_account/views/RegisterView.js'),