|
38 | 38 | """ # pylint: disable=line-too-long |
39 | 39 |
|
40 | 40 | import logging |
41 | | -import re |
42 | 41 |
|
| 42 | +import crum |
43 | 43 | from openedx_filters.filters import PipelineStep |
44 | 44 |
|
| 45 | +from .models import CourseArchiveStatus |
| 46 | + |
45 | 47 | logger = logging.getLogger(__name__) |
46 | 48 |
|
47 | 49 |
|
48 | | -class ChangeCourseAboutPageUrl(PipelineStep): |
| 50 | +class AddArchiveStatusToLearnerHomeCourseRun(PipelineStep): |
49 | 51 | """ |
50 | | - Filter to customize course about page URLs. |
51 | | -
|
52 | | - This filter demonstrates how to intercept and modify course about page URLs, |
53 | | - redirecting them to external sites or custom implementations. |
54 | | -
|
55 | | - Filter Hook Point: |
56 | | - This filter hooks into the course about page URL rendering process. |
57 | | - Register it for the filter: org.openedx.learning.course.about.render.started.v1 |
58 | | -
|
59 | | - Registration Example (in settings/common.py):: |
60 | | -
|
61 | | - def plugin_settings(settings): |
62 | | - settings.OPEN_EDX_FILTERS_CONFIG = { |
63 | | - "org.openedx.learning.course.about.render.started.v1": { |
64 | | - "pipeline": [ |
65 | | - "openedx_plugin_sample.pipeline.ChangeCourseAboutPageUrl" |
66 | | - ], |
67 | | - "fail_silently": False, |
68 | | - } |
69 | | - } |
70 | | -
|
71 | | - Filter Documentation: |
72 | | - - Available Filters: https://docs.openedx.org/projects/openedx-filters/en/latest/reference/filters.html |
73 | | - - PipelineStep: https://docs.openedx.org/projects/openedx-filters/en/latest/reference/filters-tooling.html#openedx_filters.filters.PipelineStep |
74 | | -
|
75 | | - Real-World Use Cases: |
76 | | - - Redirect to marketing site course pages |
77 | | - - Implement custom course discovery interfaces |
78 | | - - Add tracking parameters to URLs |
79 | | - - Route different course types to different platforms |
80 | | - - Implement A/B testing for course pages |
| 52 | + Customize each courseRun within a Learner Dashboard's /init API response to include the CourseArchiveStatus. |
81 | 53 | """ # noqa: E501 |
82 | 54 |
|
83 | | - def run_filter(self, url, org, **kwargs): # pylint: disable=arguments-differ |
| 55 | + def run_filter(self, serialized_courserun, **kwargs): # pylint: disable=arguments-differ |
84 | 56 | """ |
85 | | - Modify the course about page URL. |
86 | | -
|
87 | | - This method intercepts course about page URL generation and can modify |
88 | | - the destination URL based on business logic. |
| 57 | + Insert `isArchivedByLearner` into one serialized courseRun for the Learner Home /init response. |
89 | 58 |
|
90 | 59 | Args: |
91 | | - url (str): The original course about page URL |
92 | | - org (str): The organization/institution identifier |
93 | | - **kwargs: Additional context data from the platform |
| 60 | + serialized_courserun (dict): One courseRun from the serializer. Reads |
| 61 | + `courseId` (a course key string, e.g. "course-v1:edX+DemoX+Demo_Course"); |
| 62 | + all other fields are passed through unchanged. |
94 | 63 |
|
95 | 64 | Returns: |
96 | | - dict: Dictionary with same parameter names as input |
97 | | - - url (str): Modified or original URL |
98 | | - - org (str): Organization identifier (usually unchanged) |
99 | | -
|
100 | | - Raises: |
101 | | - FilterException: If processing should be halted |
102 | | -
|
103 | | - Filter Requirements: |
104 | | - - Must return dictionary with keys matching input parameters |
105 | | - - Return None to skip this filter (let other filters run) |
106 | | - - Raise FilterException to halt pipeline execution |
107 | | - - Handle all input scenarios gracefully |
108 | | -
|
109 | | - URL Pattern Matching: |
110 | | - This implementation looks for Open edX course keys in the format: |
111 | | - course-v1:ORG+COURSE+RUN (e.g., course-v1:edX+DemoX+Demo_Course) |
112 | | -
|
113 | | - Documentation: |
114 | | - - run_filter method: https://docs.openedx.org/projects/openedx-filters/en/latest/reference/filters-tooling.html#openedx_filters.filters.PipelineStep.run_filter |
| 65 | + dict: ``{"serialized_courserun": <updated dict>}``. The updated dict has the |
| 66 | + same keys as the input plus `isArchivedByLearner` (bool) -- True iff a |
| 67 | + CourseArchiveStatus row exists for the current request user and this |
| 68 | + courseId with `is_archived=True`; False otherwise (including when no row |
| 69 | + exists). |
| 70 | +
|
| 71 | + The current user is read from the active request via `crum`, so this filter only |
| 72 | + runs meaningfully inside a request cycle. Note that `isArchivedByLearner` is |
| 73 | + distinct from `isArchived`, which the platform sets based on whether the course |
| 74 | + run itself has ended. |
115 | 75 | """ # noqa: E501 |
116 | | - # Extract course ID using Open edX course key pattern |
117 | | - # Course keys follow the format: course-v1:ORG+COURSE+RUN |
118 | | - pattern = r'(?P<course_id>course-v1:[^/]+)' |
119 | | - |
120 | | - match = re.search(pattern, url) |
121 | | - if match: |
122 | | - course_id = match.group('course_id') |
123 | | - |
124 | | - # Example: Redirect to external marketing site |
125 | | - new_url = f"https://example.com/new_about_page/{course_id}" |
126 | | - |
127 | | - logger.debug( |
128 | | - f"Redirecting course about page for {course_id} from {url} to {new_url}" |
129 | | - ) |
130 | | - |
131 | | - # Return modified data |
132 | | - return {"url": new_url, "org": org} |
133 | | - |
134 | | - # No course ID found - return original data unchanged |
135 | | - logger.debug(f"No course ID found in URL {url}, leaving unchanged") |
136 | | - return {"url": url, "org": org} |
137 | | - |
138 | | - # Alternative patterns for different business logic: |
139 | | - |
140 | | - # Organization-based routing: |
141 | | - # if org == "special_org": |
142 | | - # new_url = f"https://special-site.com/courses/{course_id}" |
143 | | - # return {"url": new_url, "org": org} |
144 | | - |
145 | | - # Course type-based routing: |
146 | | - # if "MicroMasters" in course_id: |
147 | | - # new_url = f"https://micromasters.example.com/{course_id}" |
148 | | - # return {"url": new_url, "org": org} |
149 | | - |
150 | | - # A/B testing implementation: |
151 | | - # import random |
152 | | - # if random.choice([True, False]): |
153 | | - # new_url = f"https://variant-a.example.com/{course_id}" |
154 | | - # else: |
155 | | - # new_url = f"https://variant-b.example.com/{course_id}" |
156 | | - # return {"url": new_url, "org": org} |
| 76 | + request = crum.get_current_request() |
| 77 | + if not (request and request.user): |
| 78 | + return serialized_courserun |
| 79 | + try: |
| 80 | + is_archived_by_learner = CourseArchiveStatus.objects.get( |
| 81 | + user=request.user, course_id=serialized_courserun["courseId"] |
| 82 | + ).is_archived |
| 83 | + except CourseArchiveStatus.DoesNotExist: |
| 84 | + is_archived_by_learner = False |
| 85 | + return { |
| 86 | + "serialized_courserun": { |
| 87 | + **serialized_courserun, |
| 88 | + "isArchivedByLearner": is_archived_by_learner, |
| 89 | + }, |
| 90 | + } |
0 commit comments