feat!: Legacy account, profile, order history removal (#36219)

* feat!: Legacy account, profile, order history removal

This removes the legacy account and profile applications, and the order
history page. This is primarily a reapplication of #31893, which was
rolled back due to prior blockers.

FIXES: APER-3884
FIXES: openedx/public-engineering#71


Co-authored-by: Muhammad Abdullah Waheed <42172960+abdullahwaheed@users.noreply.github.com>
Co-authored-by: Bilal Qamar <59555732+BilalQamar95@users.noreply.github.com>
This commit is contained in:
Deborah Kaplan
2025-02-10 14:39:13 -05:00
committed by GitHub
parent 36c16d6952
commit 29de9b2dc4
73 changed files with 752 additions and 7391 deletions

View File

@@ -1,300 +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"),
"work_experience": _("Work experience")
}
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

View File

@@ -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 <em>My site</em> 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.")

View File

@@ -1,287 +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 requests 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.site_configuration.tests.test_util import with_site_configuration
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
@with_site_configuration(
configuration={
'extended_profile_fields': ['work_experience']
}
)
def test_context_extended_profile(self):
"""
Test that if the field is available in extended_profile configuration then the field
will be sent in response.
"""
context = account_settings_context(self.request)
extended_pofile_field = context['extended_profile_fields'][0]
assert extended_pofile_field['field_name'] == 'work_experience'
assert extended_pofile_field['field_label'] == 'Work experience'
@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.HTTPError):
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)

View File

@@ -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())

View File

@@ -5,7 +5,6 @@ from django.urls import path, re_path, include
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'users', user_api_views.UserViewSet)
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<pref_key>{UserPreference.KEY_REGEX})/users/$',