feat: add filter before course dashboard rendering process starts
* Add dashboard filter to dashboard student's view * Add tests/docs for filter's integration
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
"""
|
||||
Test that various filters are fired for models/views in the student 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 CourseEnrollmentStarted, CourseUnenrollmentStarted
|
||||
from openedx_filters.learning.filters import DashboardRenderStarted, CourseEnrollmentStarted, CourseUnenrollmentStarted
|
||||
from rest_framework import status
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
@@ -43,6 +44,72 @@ class TestUnenrollmentPipelineStep(PipelineStep):
|
||||
return {}
|
||||
|
||||
|
||||
class TestDashboardRenderPipelineStep(PipelineStep):
|
||||
"""
|
||||
Utility class used when getting steps for pipeline.
|
||||
"""
|
||||
|
||||
def run_filter(self, context, template_name): # pylint: disable=arguments-differ
|
||||
"""Pipeline step that modifies dashboard data."""
|
||||
context["course_enrollments"] = []
|
||||
return {
|
||||
"context": context,
|
||||
"template_name": template_name,
|
||||
}
|
||||
|
||||
|
||||
class TestRenderInvalidDashboard(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 dashboard render process."""
|
||||
raise DashboardRenderStarted.RenderInvalidDashboard(
|
||||
"You can't render this sites dashboard.",
|
||||
dashboard_template="static_templates/server-error.html"
|
||||
)
|
||||
|
||||
|
||||
class TestRedirectDashboardPageStep(PipelineStep):
|
||||
"""
|
||||
Utility class used when getting steps for pipeline.
|
||||
"""
|
||||
|
||||
def run_filter(self, context, template_name): # pylint: disable=arguments-differ
|
||||
"""Pipeline step that redirects before the dashboard is rendered."""
|
||||
raise DashboardRenderStarted.RedirectToPage(
|
||||
"You can't see this site's dashboard, redirecting to the correct location.",
|
||||
redirect_to="https://custom-dashboard.com",
|
||||
)
|
||||
|
||||
|
||||
class TestRedirectToAccSettingsPage(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 account settings before the dashboard is rendered."""
|
||||
raise DashboardRenderStarted.RedirectToPage(
|
||||
"You can't see this site's dashboard, redirecting to the correct location.",
|
||||
)
|
||||
|
||||
|
||||
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 changes dashboard view response before the dashboard is rendered."""
|
||||
response = HttpResponse("This is a custom response.")
|
||||
raise DashboardRenderStarted.RenderCustomResponse(
|
||||
"You can't see this site's dashboard.",
|
||||
response=response,
|
||||
)
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
class EnrollmentFiltersTest(ModuleStoreTestCase):
|
||||
"""
|
||||
@@ -235,3 +302,164 @@ class UnenrollmentFiltersTest(ModuleStoreTestCase):
|
||||
|
||||
self.assertEqual(status.HTTP_400_BAD_REQUEST, response.status_code)
|
||||
self.assertEqual("You can't un-enroll from this site.", response.content.decode("utf-8"))
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
class StudentDashboardFiltersTest(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for the Open edX Filters associated with the dashboard rendering process.
|
||||
|
||||
This class guarantees that the following filters are triggered during the students dashboard rendering:
|
||||
- DashboardRenderStarted
|
||||
"""
|
||||
|
||||
def setUp(self): # pylint: disable=arguments-differ
|
||||
super().setUp()
|
||||
self.user = UserFactory()
|
||||
self.client.login(username=self.user.username, password="test")
|
||||
self.dashboard_url = reverse("dashboard")
|
||||
self.first_course = CourseFactory.create(
|
||||
org="test1", course="course1", display_name="run1",
|
||||
)
|
||||
self.second_course = CourseFactory.create(
|
||||
org="test2", course="course2", display_name="run1",
|
||||
)
|
||||
|
||||
@override_settings(
|
||||
OPEN_EDX_FILTERS_CONFIG={
|
||||
"org.openedx.learning.dashboard.render.started.v1": {
|
||||
"pipeline": [
|
||||
"common.djangoapps.student.tests.test_filters.TestDashboardRenderPipelineStep",
|
||||
],
|
||||
"fail_silently": False,
|
||||
},
|
||||
},
|
||||
)
|
||||
def test_dashboard_render_filter_executed(self):
|
||||
"""
|
||||
Test whether the student dashboard filter is triggered before the user's
|
||||
dashboard rendering process.
|
||||
|
||||
Expected result:
|
||||
- DashboardRenderStarted is triggered and executes TestDashboardRenderPipelineStep.
|
||||
- The dashboard is rendered using the filtered enrollments list.
|
||||
"""
|
||||
CourseEnrollment.enroll(self.user, self.first_course.id)
|
||||
CourseEnrollment.enroll(self.user, self.second_course.id)
|
||||
|
||||
response = self.client.get(self.dashboard_url)
|
||||
|
||||
self.assertNotContains(response, self.first_course.id)
|
||||
self.assertNotContains(response, self.second_course.id)
|
||||
|
||||
@override_settings(
|
||||
OPEN_EDX_FILTERS_CONFIG={
|
||||
"org.openedx.learning.dashboard.render.started.v1": {
|
||||
"pipeline": [
|
||||
"common.djangoapps.student.tests.test_filters.TestRenderInvalidDashboard",
|
||||
],
|
||||
"fail_silently": False,
|
||||
},
|
||||
},
|
||||
PLATFORM_NAME="My site",
|
||||
)
|
||||
def test_dashboard_render_invalid(self):
|
||||
"""
|
||||
Test rendering an invalid template after catching PreventDashboardRender exception.
|
||||
|
||||
Expected result:
|
||||
- DashboardRenderStarted is triggered and executes TestRenderInvalidDashboard.
|
||||
- The server error template is rendered instead of the usual dashboard.
|
||||
"""
|
||||
response = self.client.get(self.dashboard_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.dashboard.render.started.v1": {
|
||||
"pipeline": [
|
||||
"common.djangoapps.student.tests.test_filters.TestRedirectDashboardPageStep",
|
||||
],
|
||||
"fail_silently": False,
|
||||
},
|
||||
},
|
||||
)
|
||||
def test_dashboard_redirect(self):
|
||||
"""
|
||||
Test redirecting to a new page after catching RedirectDashboardPage exception.
|
||||
|
||||
Expected result:
|
||||
- DashboardRenderStarted is triggered and executes TestRedirectDashboardPageStep.
|
||||
- The view response is a redirection.
|
||||
- The redirection url is the custom dashboard specified in the filter.
|
||||
"""
|
||||
response = self.client.get(self.dashboard_url)
|
||||
|
||||
self.assertEqual(status.HTTP_302_FOUND, response.status_code)
|
||||
self.assertEqual("https://custom-dashboard.com", response.url)
|
||||
|
||||
@override_settings(
|
||||
OPEN_EDX_FILTERS_CONFIG={
|
||||
"org.openedx.learning.dashboard.render.started.v1": {
|
||||
"pipeline": [
|
||||
"common.djangoapps.student.tests.test_filters.TestRedirectToAccSettingsPage",
|
||||
],
|
||||
"fail_silently": False,
|
||||
},
|
||||
},
|
||||
)
|
||||
def test_dashboard_redirect_account_settings(self):
|
||||
"""
|
||||
Test redirecting to the account settings page after catching RedirectDashboardPage exception.
|
||||
|
||||
Expected result:
|
||||
- DashboardRenderStarted is triggered and executes TestRedirectToAccSettingsPage.
|
||||
- The view response is a redirection.
|
||||
- The redirection url is the account settings (as the default when not specifying one).
|
||||
"""
|
||||
response = self.client.get(self.dashboard_url)
|
||||
|
||||
self.assertEqual(status.HTTP_302_FOUND, response.status_code)
|
||||
self.assertEqual(reverse("account_settings"), response.url)
|
||||
|
||||
@override_settings(
|
||||
OPEN_EDX_FILTERS_CONFIG={
|
||||
"org.openedx.learning.dashboard.render.started.v1": {
|
||||
"pipeline": [
|
||||
"common.djangoapps.student.tests.test_filters.TestRenderCustomResponse",
|
||||
],
|
||||
"fail_silently": False,
|
||||
},
|
||||
},
|
||||
)
|
||||
def test_dashboard_custom_response(self):
|
||||
"""
|
||||
Test returning a custom response after catching RenderCustomResponse exception.
|
||||
|
||||
Expected result:
|
||||
- DashboardRenderStarted is triggered and executes TestRenderCustomResponse.
|
||||
- The view response contains the custom response text.
|
||||
"""
|
||||
response = self.client.get(self.dashboard_url)
|
||||
|
||||
self.assertEqual("This is a custom response.", response.content.decode("utf-8"))
|
||||
|
||||
@override_settings(OPEN_EDX_FILTERS_CONFIG={})
|
||||
def test_dashboard_render_without_filter_config(self):
|
||||
"""
|
||||
Test whether the student dashboard filter is triggered before the user's
|
||||
dashboard rendering process without any modification in the app flow.
|
||||
|
||||
Expected result:
|
||||
- DashboardRenderStarted executes a noop (empty pipeline).
|
||||
- The view response is HTTP_200_OK.
|
||||
- There's no modification in the dashboard.
|
||||
"""
|
||||
CourseEnrollment.enroll(self.user, self.first_course.id)
|
||||
CourseEnrollment.enroll(self.user, self.second_course.id)
|
||||
|
||||
response = self.client.get(self.dashboard_url)
|
||||
|
||||
self.assertContains(response, self.first_course.id)
|
||||
self.assertContains(response, self.second_course.id)
|
||||
|
||||
@@ -10,6 +10,7 @@ from collections import defaultdict
|
||||
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 _
|
||||
@@ -18,6 +19,7 @@ from edx_django_utils import monitoring as monitoring_utils
|
||||
from edx_django_utils.plugins import get_plugins_view_context
|
||||
from edx_toggles.toggles import WaffleFlag
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from openedx_filters.learning.filters import DashboardRenderStarted
|
||||
from pytz import UTC
|
||||
|
||||
from lms.djangoapps.bulk_email.api import is_bulk_email_feature_enabled
|
||||
@@ -853,7 +855,22 @@ def student_dashboard(request): # lint-amnesty, pylint: disable=too-many-statem
|
||||
'resume_button_urls': resume_button_urls
|
||||
})
|
||||
|
||||
response = render_to_response('dashboard.html', context)
|
||||
dashboard_template = 'dashboard.html'
|
||||
try:
|
||||
# .. filter_implemented_name: DashboardRenderStarted
|
||||
# .. filter_type: org.openedx.learning.dashboard.render.started.v1
|
||||
context, dashboard_template = DashboardRenderStarted.run_filter(
|
||||
context=context, template_name=dashboard_template,
|
||||
)
|
||||
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'))
|
||||
except DashboardRenderStarted.RenderCustomResponse as exc:
|
||||
response = exc.response
|
||||
else:
|
||||
response = render_to_response(dashboard_template, context)
|
||||
|
||||
if show_account_activation_popup:
|
||||
response.delete_cookie(
|
||||
settings.SHOW_ACTIVATE_CTA_POPUP_COOKIE_NAME,
|
||||
|
||||
Reference in New Issue
Block a user