From 9c8760bde3351a55bcffc2d53ada8b2488d1ad31 Mon Sep 17 00:00:00 2001 From: Troy Sankey Date: Tue, 3 Mar 2026 17:27:55 -0800 Subject: [PATCH] feat: add CoursewareViewRedirectURL filter Add a new CoursewareViewRedirectURL filter to the learning subdomain. This filter is triggered before courseware views are rendered, allowing pipeline steps to append redirect URLs. The caller redirects to the first URL in the list, or continues normally if the list is empty. ENT-11544 Co-Authored-By: Claude Sonnet 4.6 --- openedx_filters/learning/filters.py | 42 +++++++++++++++++++ .../learning/tests/test_filters.py | 27 ++++++++++++ 2 files changed, 69 insertions(+) diff --git a/openedx_filters/learning/filters.py b/openedx_filters/learning/filters.py index 6db44fd3..8772c0b3 100644 --- a/openedx_filters/learning/filters.py +++ b/openedx_filters/learning/filters.py @@ -1445,3 +1445,45 @@ def run_filter(cls, schedules: QuerySet) -> QuerySet | None: """ data = super().run_pipeline(schedules=schedules) return data.get("schedules") + + +class CoursewareViewRedirectURL(OpenEdxPublicFilter): + """ + Filter used to determine whether a courseware view should redirect the user. + + Purpose: + This filter is triggered before a courseware view is rendered, allowing pipeline + steps to append redirect URLs to ``redirect_urls``. If the list is non-empty after + the pipeline runs, the view redirects the user to the first URL in the list. + + Filter Type: + org.openedx.learning.courseware.view.redirect_url.requested.v1 + + Trigger: + - Repository: openedx/edx-platform + - Path: lms/djangoapps/courseware/decorators.py + - Function or Method: courseware_view_redirect + """ + + filter_type = "org.openedx.learning.courseware.view.redirect_url.requested.v1" + + @classmethod + def run_filter(cls, redirect_urls: list, request: Any, course_key: Any) -> tuple: + """ + Process the inputs using the configured pipeline steps. + + Arguments: + redirect_urls (list): initial list of redirect URLs (typically empty). + request (HttpRequest): the current Django HTTP request. + course_key (CourseKey): the course key for the view being accessed. + + Returns: + tuple[list, HttpRequest, CourseKey]: + - list: the (possibly extended) list of redirect URLs. + - HttpRequest: the request object (unchanged). + - CourseKey: the course key (unchanged). + """ + data = super().run_pipeline( + redirect_urls=redirect_urls, request=request, course_key=course_key + ) + return data.get("redirect_urls"), data.get("request"), data.get("course_key") diff --git a/openedx_filters/learning/tests/test_filters.py b/openedx_filters/learning/tests/test_filters.py index b4ccf69a..7fc166ca 100644 --- a/openedx_filters/learning/tests/test_filters.py +++ b/openedx_filters/learning/tests/test_filters.py @@ -21,6 +21,7 @@ CourseHomeUrlCreationStarted, CourseRunAPIRenderStarted, CourseUnenrollmentStarted, + CoursewareViewRedirectURL, DashboardRenderStarted, IDVPageURLRequested, InstructorDashboardRenderStarted, @@ -801,3 +802,29 @@ def test_schedule_requested(self): result = ScheduleQuerySetRequested.run_filter(schedules) self.assertEqual(schedules, result) + + +class TestCoursewareViewRedirectURL(TestCase): + """ + Test class to verify standard behavior of the CoursewareViewRedirectURL filter. + """ + + def test_returns_redirect_urls_unchanged_when_no_pipeline_steps(self): + """ + Test CoursewareViewRedirectURL filter behavior under normal conditions. + + Expected behavior: + - The filter returns the redirect_urls list unchanged when no pipeline steps modify it. + - The request and course_key are returned unchanged. + """ + redirect_urls = [] + request = Mock() + course_key = Mock() + + result_urls, result_request, result_course_key = CoursewareViewRedirectURL.run_filter( + redirect_urls, request, course_key + ) + + self.assertEqual(redirect_urls, result_urls) + self.assertEqual(request, result_request) + self.assertEqual(course_key, result_course_key)