Skip to content

Commit

Permalink
feat: add filter before course dashboard rendering process starts
Browse files Browse the repository at this point in the history
* Add dashboard filter to dashboard student's view
* Add tests/docs for filter's integration
  • Loading branch information
mariajgrimaldi committed May 9, 2022
1 parent baf1cbc commit 895a649
Show file tree
Hide file tree
Showing 2 changed files with 247 additions and 2 deletions.
230 changes: 229 additions & 1 deletion common/djangoapps/student/tests/test_filters.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -43,6 +44,72 @@ def run_filter(self, enrollment): # pylint: disable=arguments-differ
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):
"""
Expand Down Expand Up @@ -235,3 +302,164 @@ def test_unenrollment_blocked_by_filter(self):

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)
19 changes: 18 additions & 1 deletion common/djangoapps/student/views/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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 _
Expand All @@ -18,6 +19,7 @@
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
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 895a649

Please sign in to comment.