Skip to content

Commit d7ca7c1

Browse files
authored
feat: add openedx-filters hook to account settings before rendering it context (#31295)
1 parent 32e792d commit d7ca7c1

File tree

5 files changed

+265
-4
lines changed

5 files changed

+265
-4
lines changed

openedx/core/djangoapps/user_api/accounts/settings_views.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
from django.conf import settings
99
from django.contrib import messages
1010
from django.contrib.auth.decorators import login_required
11+
from django.http import HttpResponseRedirect
1112
from django.shortcuts import redirect
1213
from django.urls import reverse
1314
from django.utils.translation import gettext as _
1415
from django.views.decorators.http import require_http_methods
1516
from django_countries import countries
1617

18+
from openedx_filters.learning.filters import AccountSettingsRenderStarted
1719
from common.djangoapps import third_party_auth
1820
from common.djangoapps.edxmako.shortcuts import render_to_response
1921
from common.djangoapps.student.models import UserProfile
@@ -72,7 +74,25 @@ def account_settings(request):
7274
return redirect(url)
7375

7476
context = account_settings_context(request)
75-
return render_to_response('student_account/account_settings.html', context)
77+
78+
account_settings_template = 'student_account/account_settings.html'
79+
80+
try:
81+
# .. filter_implemented_name: AccountSettingsRenderStarted
82+
# .. filter_type: org.openedx.learning.student.settings.render.started.v1
83+
context, account_settings_template = AccountSettingsRenderStarted.run_filter(
84+
context=context, template_name=account_settings_template,
85+
)
86+
except AccountSettingsRenderStarted.RenderInvalidAccountSettings as exc:
87+
response = render_to_response(exc.account_settings_template, exc.template_context)
88+
except AccountSettingsRenderStarted.RedirectToPage as exc:
89+
response = HttpResponseRedirect(exc.redirect_to or reverse('dashboard'))
90+
except AccountSettingsRenderStarted.RenderCustomResponse as exc:
91+
response = exc.response
92+
else:
93+
response = render_to_response(account_settings_template, context)
94+
95+
return response
7696

7797

7898
def account_settings_context(request):
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
"""
2+
Test that various filters are fired for views in the certificates app.
3+
"""
4+
from django.http import HttpResponse
5+
from django.test import override_settings
6+
from django.urls import reverse
7+
from openedx_filters import PipelineStep
8+
from openedx_filters.learning.filters import AccountSettingsRenderStarted
9+
from rest_framework import status
10+
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
11+
12+
from openedx.core.djangolib.testing.utils import skip_unless_lms
13+
from common.djangoapps.student.tests.factories import UserFactory
14+
15+
16+
class TestRenderInvalidAccountSettings(PipelineStep):
17+
"""
18+
Utility class used when getting steps for pipeline.
19+
"""
20+
21+
def run_filter(self, context, template_name): # pylint: disable=arguments-differ
22+
"""
23+
Pipeline step that stops the course about render process.
24+
"""
25+
raise AccountSettingsRenderStarted.RenderInvalidAccountSettings(
26+
"You can't access the account settings page.",
27+
account_settings_template="static_templates/server-error.html",
28+
)
29+
30+
31+
class TestRedirectToPage(PipelineStep):
32+
"""
33+
Utility class used when getting steps for pipeline.
34+
"""
35+
36+
def run_filter(self, context, template_name): # pylint: disable=arguments-differ
37+
"""
38+
Pipeline step that redirects to dashboard before rendering the account settings page.
39+
40+
When raising RedirectToPage, this filter uses a redirect_to field handled by
41+
the course about view that redirects to that URL.
42+
"""
43+
raise AccountSettingsRenderStarted.RedirectToPage(
44+
"You can't access this page, redirecting to dashboard.",
45+
redirect_to="/courses",
46+
)
47+
48+
49+
class TestRedirectToDefaultPage(PipelineStep):
50+
"""
51+
Utility class used when getting steps for pipeline.
52+
"""
53+
54+
def run_filter(self, context, template_name): # pylint: disable=arguments-differ
55+
"""
56+
Pipeline step that redirects to dashboard before rendering the account settings page.
57+
58+
When raising RedirectToPage, this filter uses a redirect_to field handled by
59+
the course about view that redirects to that URL.
60+
"""
61+
raise AccountSettingsRenderStarted.RedirectToPage(
62+
"You can't access this page, redirecting to dashboard."
63+
)
64+
65+
66+
class TestRenderCustomResponse(PipelineStep):
67+
"""
68+
Utility class used when getting steps for pipeline.
69+
"""
70+
71+
def run_filter(self, context, template_name): # pylint: disable=arguments-differ
72+
"""Pipeline step that returns a custom response when rendering the account settings page."""
73+
response = HttpResponse("Here's the text of the web page.")
74+
raise AccountSettingsRenderStarted.RenderCustomResponse(
75+
"You can't access this page.",
76+
response=response,
77+
)
78+
79+
80+
class TestAccountSettingsRender(PipelineStep):
81+
"""
82+
Utility class used when getting steps for pipeline.
83+
"""
84+
85+
def run_filter(self, context, template_name): # pylint: disable=arguments-differ
86+
"""Pipeline step that returns a custom response when rendering the account settings page."""
87+
template_name = 'static_templates/about.html'
88+
return {
89+
"context": context, "template_name": template_name,
90+
}
91+
92+
93+
@skip_unless_lms
94+
class TestAccountSettingsFilters(SharedModuleStoreTestCase):
95+
"""
96+
Tests for the Open edX Filters associated with the account settings proccess.
97+
98+
This class guarantees that the following filters are triggered during the user's account settings rendering:
99+
100+
- AccountSettingsRenderStarted
101+
"""
102+
def setUp(self): # pylint: disable=arguments-differ
103+
super().setUp()
104+
self.user = UserFactory.create(
105+
username="somestudent",
106+
first_name="Student",
107+
last_name="Person",
108+
109+
is_active=True,
110+
password="password",
111+
)
112+
self.client.login(username=self.user.username, password="password")
113+
self.account_settings_url = '/account/settings'
114+
115+
@override_settings(
116+
OPEN_EDX_FILTERS_CONFIG={
117+
"org.openedx.learning.student.settings.render.started.v1": {
118+
"pipeline": [
119+
"openedx.core.djangoapps.user_api.accounts.tests.test_filters.TestAccountSettingsRender",
120+
],
121+
"fail_silently": False,
122+
},
123+
},
124+
)
125+
def test_account_settings_render_filter_executed(self):
126+
"""
127+
Test whether the account settings filter is triggered before the user's
128+
account settings page is rendered.
129+
130+
Expected result:
131+
- AccountSettingsRenderStarted is triggered and executes TestAccountSettingsRender
132+
"""
133+
response = self.client.get(self.account_settings_url)
134+
self.assertEqual(response.status_code, status.HTTP_200_OK)
135+
self.assertContains(response, "This page left intentionally blank. Feel free to add your own content.")
136+
137+
@override_settings(
138+
OPEN_EDX_FILTERS_CONFIG={
139+
"org.openedx.learning.student.settings.render.started.v1": {
140+
"pipeline": [
141+
"openedx.core.djangoapps.user_api.accounts.tests.test_filters.TestRenderInvalidAccountSettings", # pylint: disable=line-too-long
142+
],
143+
"fail_silently": False,
144+
},
145+
},
146+
PLATFORM_NAME="My site",
147+
)
148+
def test_account_settings_render_alternative(self):
149+
"""
150+
Test whether the account settings filter is triggered before the user's
151+
account settings page is rendered.
152+
153+
Expected result:
154+
- AccountSettingsRenderStarted is triggered and executes TestRenderInvalidAccountSettings # pylint: disable=line-too-long
155+
"""
156+
response = self.client.get(self.account_settings_url)
157+
158+
self.assertContains(response, "There has been a 500 error on the <em>My site</em> servers")
159+
160+
@override_settings(
161+
OPEN_EDX_FILTERS_CONFIG={
162+
"org.openedx.learning.student.settings.render.started.v1": {
163+
"pipeline": [
164+
"openedx.core.djangoapps.user_api.accounts.tests.test_filters.TestRenderCustomResponse",
165+
],
166+
"fail_silently": False,
167+
},
168+
},
169+
)
170+
def test_account_settings_render_custom_response(self):
171+
"""
172+
Test whether the account settings filter is triggered before the user's
173+
account settings page is rendered.
174+
175+
Expected result:
176+
- AccountSettingsRenderStarted is triggered and executes TestRenderCustomResponse
177+
"""
178+
response = self.client.get(self.account_settings_url)
179+
180+
self.assertEqual(response.content, b"Here's the text of the web page.")
181+
182+
@override_settings(
183+
OPEN_EDX_FILTERS_CONFIG={
184+
"org.openedx.learning.student.settings.render.started.v1": {
185+
"pipeline": [
186+
"openedx.core.djangoapps.user_api.accounts.tests.test_filters.TestRedirectToPage",
187+
],
188+
"fail_silently": False,
189+
},
190+
},
191+
)
192+
def test_account_settings_redirect_to_page(self):
193+
"""
194+
Test whether the account settings filter is triggered before the user's
195+
account settings page is rendered.
196+
197+
Expected result:
198+
- AccountSettingsRenderStarted is triggered and executes TestRedirectToPage
199+
"""
200+
response = self.client.get(self.account_settings_url)
201+
202+
self.assertEqual(response.status_code, status.HTTP_302_FOUND)
203+
self.assertEqual('/courses', response.url)
204+
205+
@override_settings(
206+
OPEN_EDX_FILTERS_CONFIG={
207+
"org.openedx.learning.student.settings.render.started.v1": {
208+
"pipeline": [
209+
"openedx.core.djangoapps.user_api.accounts.tests.test_filters.TestRedirectToDefaultPage",
210+
],
211+
"fail_silently": False,
212+
},
213+
},
214+
)
215+
def test_account_settings_redirect_default(self):
216+
"""
217+
Test whether the account settings filter is triggered before the user's
218+
account settings page is rendered.
219+
220+
Expected result:
221+
- AccountSettingsRenderStarted is triggered and executes TestRedirectToDefaultPage
222+
"""
223+
response = self.client.get(self.account_settings_url)
224+
225+
self.assertEqual(response.status_code, status.HTTP_302_FOUND)
226+
self.assertEqual(f"{reverse('dashboard')}", response.url)
227+
228+
@override_settings(OPEN_EDX_FILTERS_CONFIG={})
229+
def test_account_settings_render_without_filter_config(self):
230+
"""
231+
Test whether the course about filter is triggered before the course about
232+
render without affecting its execution flow.
233+
234+
Expected result:
235+
- AccountSettingsRenderStarted executes a noop (empty pipeline). Without any
236+
modification comparing it with the effects of TestAccountSettingsRender.
237+
- The view response is HTTP_200_OK.
238+
"""
239+
response = self.client.get(self.account_settings_url)
240+
241+
self.assertNotContains(response, "This page left intentionally blank. Feel free to add your own content.")

requirements/edx/base.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,7 @@ openedx-events==5.1.0
761761
# via
762762
# -r requirements/edx/base.in
763763
# edx-event-bus-kafka
764-
openedx-filters==1.1.0
764+
openedx-filters==1.2.0
765765
# via
766766
# -r requirements/edx/base.in
767767
# lti-consumer-xblock

requirements/edx/development.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1021,7 +1021,7 @@ openedx-events==5.1.0
10211021
# via
10221022
# -r requirements/edx/testing.txt
10231023
# edx-event-bus-kafka
1024-
openedx-filters==1.1.0
1024+
openedx-filters==1.2.0
10251025
# via
10261026
# -r requirements/edx/testing.txt
10271027
# lti-consumer-xblock

requirements/edx/testing.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -969,7 +969,7 @@ openedx-events==5.1.0
969969
# via
970970
# -r requirements/edx/base.txt
971971
# edx-event-bus-kafka
972-
openedx-filters==1.1.0
972+
openedx-filters==1.2.0
973973
# via
974974
# -r requirements/edx/base.txt
975975
# lti-consumer-xblock

0 commit comments

Comments
 (0)