From 2d4cbb0c4e5009295973bf87383e70dad0f7ebce Mon Sep 17 00:00:00 2001 From: Henrry Pulgarin <39854568+Henrrypg@users.noreply.github.com> Date: Thu, 9 Mar 2023 12:17:44 -0500 Subject: [PATCH] feat: add openedx-filters hook to account settings before rendering it context (#31295) --- .../user_api/accounts/settings_views.py | 22 +- .../user_api/accounts/tests/test_filters.py | 241 ++++++++++++++++++ requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/testing.txt | 2 +- 5 files changed, 265 insertions(+), 4 deletions(-) create mode 100644 openedx/core/djangoapps/user_api/accounts/tests/test_filters.py diff --git a/openedx/core/djangoapps/user_api/accounts/settings_views.py b/openedx/core/djangoapps/user_api/accounts/settings_views.py index ae7bca30144..002695d4e33 100644 --- a/openedx/core/djangoapps/user_api/accounts/settings_views.py +++ b/openedx/core/djangoapps/user_api/accounts/settings_views.py @@ -8,12 +8,14 @@ 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 @@ -72,7 +74,25 @@ def account_settings(request): return redirect(url) context = account_settings_context(request) - return render_to_response('student_account/account_settings.html', context) + + 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): diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_filters.py b/openedx/core/djangoapps/user_api/accounts/tests/test_filters.py new file mode 100644 index 00000000000..782549aea0b --- /dev/null +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_filters.py @@ -0,0 +1,241 @@ +""" +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/requirements/edx/base.txt b/requirements/edx/base.txt index 7fee77e894c..52c664b7510 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -764,7 +764,7 @@ openedx-events==0.13.0 # -c requirements/edx/../constraints.txt # -r requirements/edx/base.in # edx-event-bus-kafka -openedx-filters==1.0.0 +openedx-filters==1.2.0 # via # -r requirements/edx/base.in # lti-consumer-xblock diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 081c92e6ac3..866b48738f2 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -996,7 +996,7 @@ openedx-events==0.13.0 # -c requirements/edx/../constraints.txt # -r requirements/edx/testing.txt # edx-event-bus-kafka -openedx-filters==1.0.0 +openedx-filters==1.2.0 # via # -r requirements/edx/testing.txt # lti-consumer-xblock diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index f4766737324..9ad4021a67d 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -947,7 +947,7 @@ openedx-events==0.13.0 # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # edx-event-bus-kafka -openedx-filters==1.0.0 +openedx-filters==1.2.0 # via # -r requirements/edx/base.txt # lti-consumer-xblock