-
Notifications
You must be signed in to change notification settings - Fork 4.2k
feat: add paginated HomePageCoursesV2 view with filtering & ordering #34173
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
Merged
Merged
Changes from all commits
Commits
Show all changes
38 commits
Select commit
Hold shift + click to select a range
97aeed8
feat: add CourseHomeCoursesV2 with filtering & ordering capabilities
mariajgrimaldi 6d218c2
refactor: address PR reviews
mariajgrimaldi 12cbacf
fix: add missing blank line
mariajgrimaldi 5471f2e
fix: address PR quality issues
mariajgrimaldi 8b02daa
fix: cast filter as sequence
mariajgrimaldi e9bef80
fix: address PR reviews
mariajgrimaldi eefd67c
refactor: remove list instantiation for course_filter
mariajgrimaldi 108517f
refactor: address PR reviews
mariajgrimaldi 37acb54
refactor!: remove checking for queryset emptiness
mariajgrimaldi fc47dab
refactor!: use existing functions for getting course objects
mariajgrimaldi 2f31735
refactor: move filter section to its own method
mariajgrimaldi ed21fe1
fix: address testing errors
mariajgrimaldi 4bf0a09
feat: add pagination to the HomePageCoursesV2 API (#34175)
mariajgrimaldi b662111
test: add same v1 test with adjustments for new output format
mariajgrimaldi 2b54de7
test: add test suite for home page courses v2
mariajgrimaldi a298b3f
fix: address test failures
mariajgrimaldi 06b6bf4
fix: go back to course overview queryset filtering
mariajgrimaldi f295535
fix: address test failures
mariajgrimaldi 70c92e2
fix: address test failures
mariajgrimaldi a58b00a
fix: address test failures
mariajgrimaldi d7f2f2f
refactor: read from mysql when homepage courses API is on
mariajgrimaldi fbdeb85
Revert "fix: address test failures"
mariajgrimaldi 8b306da
Revert "fix: address test failures"
mariajgrimaldi b5a5f36
fix: address test failures
mariajgrimaldi 4aeee4e
fix: import missing check_mongo_calls
mariajgrimaldi 46d9feb
refactor: modify tests to match latest changes in API helpers
mariajgrimaldi 8fbd800
docs: add in-line comment explaining why read from mysql
mariajgrimaldi a9d13d5
refactor: register URL if and only if homepage course API v2 is enabled
mariajgrimaldi 73a9586
fix: correct variable name according feature toggle
mariajgrimaldi 8e6b1f5
refactor: return not found when feature not enabled
mariajgrimaldi fb848f9
docs: update method docstrings
mariajgrimaldi d1e5b99
refactor: address PR reviews
mariajgrimaldi f808777
Revert "Revert "fix: address test failures""
mariajgrimaldi 084adfa
refactor: turn on feature as default
mariajgrimaldi 9666b1d
Revert "Revert "fix: address test failures""
mariajgrimaldi 3fb4b67
fix: address test failures
mariajgrimaldi 7923987
fix: address test (quality) failures
mariajgrimaldi 93e55c8
fix: address test failures
mariajgrimaldi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
3 changes: 3 additions & 0 deletions
3
cms/djangoapps/contentstore/rest_api/v2/serializers/__init__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| """Module for v2 serializers.""" | ||
|
|
||
| from cms.djangoapps.contentstore.rest_api.v2.serializers.home import CourseHomeTabSerializerV2 |
68 changes: 68 additions & 0 deletions
68
cms/djangoapps/contentstore/rest_api/v2/serializers/home.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| """ | ||
| API Serializers for course home V2 API. | ||
| """ | ||
| from django.conf import settings | ||
| from rest_framework import serializers | ||
|
|
||
| from cms.djangoapps.contentstore.utils import get_lms_link_for_item, reverse_course_url | ||
| from cms.djangoapps.contentstore.views.course import _get_rerun_link_for_item | ||
| from openedx.core.lib.api.serializers import CourseKeyField | ||
|
|
||
|
|
||
| class UnsucceededCourseSerializerV2(serializers.Serializer): | ||
| """Serializer for unsucceeded course.""" | ||
|
|
||
| display_name = serializers.CharField() | ||
| course_key = CourseKeyField() | ||
| org = serializers.CharField() | ||
| number = serializers.CharField() | ||
| run = serializers.CharField() | ||
| is_failed = serializers.BooleanField() | ||
| is_in_progress = serializers.BooleanField() | ||
| dismiss_link = serializers.CharField() | ||
|
|
||
|
|
||
| class CourseCommonSerializerV2(serializers.Serializer): | ||
| """Serializer for course common fields V2.""" | ||
|
|
||
| course_key = CourseKeyField(source='id') | ||
| display_name = serializers.CharField() | ||
| lms_link = serializers.SerializerMethodField() | ||
| cms_link = serializers.SerializerMethodField() | ||
| number = serializers.CharField() | ||
| org = serializers.CharField() | ||
| rerun_link = serializers.SerializerMethodField() | ||
| run = serializers.CharField(source='id.run') | ||
| url = serializers.SerializerMethodField() | ||
| is_active = serializers.SerializerMethodField() | ||
|
|
||
| def get_lms_link(self, obj): | ||
| """Get LMS link for course.""" | ||
| return get_lms_link_for_item(obj.location) | ||
|
|
||
| def get_cms_link(self, obj): | ||
| """Get CMS link for course.""" | ||
| return f"//{settings.CMS_BASE}{reverse_course_url('course_handler', obj.id)}" | ||
|
|
||
| def get_rerun_link(self, obj): | ||
| """Get rerun link for course.""" | ||
| return _get_rerun_link_for_item(obj.id) | ||
|
|
||
| def get_url(self, obj): | ||
| """Get URL from the course handler.""" | ||
| return reverse_course_url('course_handler', obj.id) | ||
|
|
||
| def get_is_active(self, obj): | ||
| """Get whether the course is active or not.""" | ||
| return not obj.has_ended() | ||
|
|
||
|
|
||
| class CourseHomeTabSerializerV2(serializers.Serializer): | ||
| """Serializer for course home tab V2 with unsucceeded courses and in process course actions.""" | ||
|
|
||
| courses = CourseCommonSerializerV2(required=False, many=True) | ||
| in_process_course_actions = UnsucceededCourseSerializerV2( | ||
| many=True, | ||
| required=False, | ||
| allow_null=True | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| """Contenstore API v2 URLs.""" | ||
|
|
||
| from django.urls import path | ||
|
|
||
| from cms.djangoapps.contentstore.rest_api.v2.views import HomePageCoursesViewV2 | ||
|
|
||
| app_name = "v2" | ||
|
|
||
| urlpatterns = [ | ||
| path( | ||
| "home/courses", | ||
| HomePageCoursesViewV2.as_view(), | ||
| name="courses", | ||
| ), | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| """Module for v2 views.""" | ||
|
|
||
| from cms.djangoapps.contentstore.rest_api.v2.views.home import HomePageCoursesViewV2 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| """HomePageCoursesViewV2 APIView for getting content available to the logged in user.""" | ||
| import edx_api_doc_tools as apidocs | ||
| from collections import OrderedDict | ||
| from django.conf import settings | ||
| from django.http import HttpResponseNotFound | ||
| from rest_framework.response import Response | ||
| from rest_framework.request import Request | ||
| from rest_framework.views import APIView | ||
| from rest_framework.pagination import PageNumberPagination | ||
|
|
||
| from openedx.core.lib.api.view_utils import view_auth_classes | ||
|
|
||
| from cms.djangoapps.contentstore.utils import get_course_context_v2 | ||
| from cms.djangoapps.contentstore.rest_api.v2.serializers import CourseHomeTabSerializerV2 | ||
|
|
||
|
|
||
| class HomePageCoursesPaginator(PageNumberPagination): | ||
| """Custom paginator for the home page courses view version 2.""" | ||
|
|
||
| def get_paginated_response(self, data): | ||
| """Return a paginated style `Response` object for the given output data.""" | ||
| return Response(OrderedDict([ | ||
| ('count', self.page.paginator.count), | ||
| ('num_pages', self.page.paginator.num_pages), | ||
| ('next', self.get_next_link()), | ||
| ('previous', self.get_previous_link()), | ||
| ('results', data), | ||
| ])) | ||
|
|
||
| def paginate_queryset(self, queryset, request, view=None): | ||
| """ | ||
| Paginate a queryset if required, either returning a page object, | ||
| or `None` if pagination is not configured for this view. | ||
|
|
||
| This method is a modified version of the original `paginate_queryset` method | ||
| from the `PageNumberPagination` class. The original method was modified to | ||
| handle the case where the `queryset` is a `filter` object. | ||
| """ | ||
| if isinstance(queryset, filter): | ||
| queryset = list(queryset) | ||
|
|
||
| return super().paginate_queryset(queryset, request, view) | ||
|
|
||
|
|
||
| @view_auth_classes(is_authenticated=True) | ||
| class HomePageCoursesViewV2(APIView): | ||
| """View for getting all courses available to the logged in user.""" | ||
|
|
||
| @apidocs.schema( | ||
| parameters=[ | ||
| apidocs.string_parameter( | ||
| "org", | ||
| apidocs.ParameterLocation.QUERY, | ||
| description="Query param to filter by course org", | ||
| ), | ||
| apidocs.string_parameter( | ||
| "search", | ||
| apidocs.ParameterLocation.QUERY, | ||
| description="Query param to filter by course name, org, or number", | ||
| ), | ||
| apidocs.string_parameter( | ||
mariajgrimaldi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| "order", | ||
| apidocs.ParameterLocation.QUERY, | ||
| description="Query param to order by course name, org, or number", | ||
| ), | ||
| apidocs.string_parameter( | ||
| "active_only", | ||
mariajgrimaldi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| apidocs.ParameterLocation.QUERY, | ||
| description="Query param to filter by active courses only", | ||
| ), | ||
| apidocs.string_parameter( | ||
| "archived_only", | ||
mariajgrimaldi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| apidocs.ParameterLocation.QUERY, | ||
| description="Query param to filter by archived courses only", | ||
| ), | ||
| apidocs.string_parameter( | ||
| "page", | ||
| apidocs.ParameterLocation.QUERY, | ||
| description="Query param to paginate the courses", | ||
| ), | ||
| ], | ||
| responses={ | ||
| 200: CourseHomeTabSerializerV2, | ||
| 401: "The requester is not authenticated.", | ||
| }, | ||
| ) | ||
| def get(self, request: Request): | ||
| """ | ||
| Get an object containing all courses. | ||
|
|
||
| **Example Request** | ||
|
|
||
| GET /api/contentstore/v2/home/courses | ||
| GET /api/contentstore/v2/home/courses?org=edX | ||
felipemontoya marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| GET /api/contentstore/v2/home/courses?search=E2E | ||
| GET /api/contentstore/v2/home/courses?order=-org | ||
| GET /api/contentstore/v2/home/courses?active_only=true | ||
| GET /api/contentstore/v2/home/courses?archived_only=true | ||
| GET /api/contentstore/v2/home/courses?page=2 | ||
|
|
||
| **Response Values** | ||
|
|
||
| If the request is successful, an HTTP 200 "OK" response is returned. | ||
|
|
||
| The HTTP 200 response contains a single dict that contains keys that | ||
| are the course's home. | ||
|
|
||
| **Example Response** | ||
|
|
||
| ```json | ||
| { | ||
| "courses": [ | ||
| { | ||
mariajgrimaldi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| "course_key": "course-v1:edX+E2E-101+course", | ||
| "display_name": "E2E Test Course", | ||
| "lms_link": "//localhost:18000/courses/course-v1:edX+E2E-101+course", | ||
| "cms_link": "//localhost:18010/course/course-v1:edX+E2E-101+course", | ||
| "number": "E2E-101", | ||
| "org": "edX", | ||
| "rerun_link": "/course_rerun/course-v1:edX+E2E-101+course", | ||
| "run": "course", | ||
| "url": "/course/course-v1:edX+E2E-101+course", | ||
| "is_active": true | ||
| }, | ||
| ], | ||
| "in_process_course_actions": [], | ||
| } | ||
| ``` | ||
|
|
||
| if the `ENABLE_HOME_PAGE_COURSE_API_V2` feature flag is not enabled, an HTTP 404 "Not Found" response | ||
| is returned. | ||
| """ | ||
| if not settings.FEATURES.get('ENABLE_HOME_PAGE_COURSE_API_V2', False): | ||
| return HttpResponseNotFound() | ||
|
|
||
| courses, in_process_course_actions = get_course_context_v2(request) | ||
| paginator = HomePageCoursesPaginator() | ||
| courses_page = paginator.paginate_queryset( | ||
| courses, | ||
| self.request, | ||
| view=self | ||
| ) | ||
| serializer = CourseHomeTabSerializerV2({ | ||
| 'courses': courses_page, | ||
| 'in_process_course_actions': in_process_course_actions, | ||
| }) | ||
| return paginator.get_paginated_response(serializer.data) | ||
Empty file.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.