-
Notifications
You must be signed in to change notification settings - Fork 26
feat: add DiscountEligibilityCheckRequested filter #335
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
e5a649e
72cbf06
68489dd
17f623e
434bd19
017e33c
6af767f
0b26a0a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1445,3 +1445,48 @@ def run_filter(cls, schedules: QuerySet) -> QuerySet | None: | |
| """ | ||
| data = super().run_pipeline(schedules=schedules) | ||
| return data.get("schedules") | ||
|
|
||
|
|
||
| class DiscountEligibilityCheckRequested(OpenEdxPublicFilter): | ||
| """ | ||
| Filter used to allow plugins to mark a user as ineligible for a course discount. | ||
|
|
||
| Purpose: | ||
| This filter is triggered during discount applicability checks, just before the | ||
| final eligibility decision is returned to the caller. Pipeline steps may set | ||
| ``is_eligible`` to ``False`` to exclude a user from receiving a discount. | ||
|
|
||
| Filter Type: | ||
| org.openedx.learning.discount.eligibility.check.requested.v1 | ||
|
|
||
| Trigger: | ||
| - Repository: openedx/edx-platform | ||
| - Path: openedx/features/discounts/applicability.py | ||
| - Function or Method: can_receive_discount, can_show_streak_discount_coupon | ||
| """ | ||
|
|
||
| filter_type = "org.openedx.learning.discount.eligibility.check.requested.v1" | ||
|
|
||
| @classmethod | ||
| def run_filter( | ||
| cls, | ||
| user: Any, | ||
| course_key: Any, | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The docstring below already names |
||
| is_eligible: bool, | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: since it only takes one pipeline step to deem the entire discount ineligible, every pipeline step implementation must take care to skip itself when We could make pipeline steps simply raise an This has the side-benefit of providing a clean way to pass error messages from the caller without polluting the Notably: class CoursewareAccessChecksRequested(OpenEdxPublicFilter):
[...]
class PreventCoursewareAccess(OpenEdxFilterException):
[...]
def __init__(
self,
error_code: str,
developer_message: str,
user_message: str,
) -> None:
super().__init__()
self.error_code = error_code
self.developer_message = developer_message
self.user_message = user_message |
||
| ) -> tuple: | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. make type more explicit.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tighten the return annotation from bare There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done for
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You actually can use AbstractBaseUser from django. See my draft PR for a different ticket: https://github.com/openedx/openedx-filters/pull/336/changes#diff-0c79edd111d62405dc0829ac0b0b971c73b410de87c0133a7190b3de08b40ed9 from django.contrib.auth.base_user import AbstractBaseUser
[...]
def run_filter(cls, user: AbstractBaseUser, course_key: CourseKey) -> tuple[AbstractBaseUser, CourseKey]: |
||
| """ | ||
| Process the inputs using the configured pipeline steps. | ||
|
|
||
| Arguments: | ||
| user (User): the Django User being checked for discount eligibility. | ||
| course_key (CourseKey or course object): identifies the course. | ||
| is_eligible (bool): the current eligibility status before plugin evaluation. | ||
|
|
||
| Returns: | ||
| tuple[User, CourseKey, bool]: | ||
| - User: the Django User object (unchanged). | ||
| - CourseKey: the course key (unchanged). | ||
| - bool: the (possibly overridden) eligibility flag. | ||
| """ | ||
| data = super().run_pipeline(user=user, course_key=course_key, is_eligible=is_eligible) | ||
| return data.get("user"), data.get("course_key"), data.get("is_eligible") | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use strict dict access here: |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ | |
| CourseRunAPIRenderStarted, | ||
| CourseUnenrollmentStarted, | ||
| DashboardRenderStarted, | ||
| DiscountEligibilityCheckRequested, | ||
| IDVPageURLRequested, | ||
| InstructorDashboardRenderStarted, | ||
| ORASubmissionViewRenderStarted, | ||
|
|
@@ -801,3 +802,31 @@ def test_schedule_requested(self): | |
| result = ScheduleQuerySetRequested.run_filter(schedules) | ||
|
|
||
| self.assertEqual(schedules, result) | ||
|
|
||
|
|
||
| class TestDiscountEligibilityCheckRequestedFilter(TestCase): | ||
| """ | ||
| Tests for the DiscountEligibilityCheckRequested filter. | ||
| """ | ||
|
|
||
| def test_filter_type(self): | ||
| self.assertEqual( | ||
| DiscountEligibilityCheckRequested.filter_type, | ||
| "org.openedx.learning.discount.eligibility.check.requested.v1", | ||
| ) | ||
|
|
||
| def test_run_filter_returns_false_when_pipeline_sets_ineligible(self): | ||
| user = Mock() | ||
| course_key = Mock() | ||
|
|
||
| with patch( | ||
| "openedx_filters.tooling.OpenEdxPublicFilter.run_pipeline", | ||
| return_value={"user": user, "course_key": course_key, "is_eligible": False}, | ||
| ): | ||
| returned_user, returned_course_key, is_eligible = DiscountEligibilityCheckRequested.run_filter( | ||
| user=user, course_key=course_key, is_eligible=True | ||
| ) | ||
|
|
||
| self.assertFalse(is_eligible) | ||
| self.assertIs(returned_user, user) | ||
| self.assertIs(returned_course_key, course_key) | ||
|
Comment on lines
+979
to
+989
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per the minimal-test convention from #334, the canonical test for a new filter in this repo verifies inputs pass through unchanged when no pipeline is configured — mirror |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should reference
openedx/openedx-platform, not the legacyopenedx/edx-platformpath.