Skip to content

Commit b4d8143

Browse files
Revert "[FC-0018] feat: Standardize HomePageCoursesView (#38366)" (#38682)
This reverts commit 1a60c23.
1 parent 1a60c23 commit b4d8143

5 files changed

Lines changed: 7 additions & 341 deletions

File tree

cms/djangoapps/contentstore/rest_api/v1/urls.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from django.conf import settings
44
from django.urls import path, re_path
5-
from rest_framework.routers import DefaultRouter
65

76
from openedx.core.constants import COURSE_ID_PATTERN
87

@@ -24,7 +23,6 @@
2423
HomePageCoursesView,
2524
HomePageLibrariesView,
2625
HomePageView,
27-
HomeViewSet,
2826
ProctoredExamSettingsView,
2927
ProctoringErrorsView,
3028
VideoDownloadView,
@@ -36,13 +34,7 @@
3634

3735
VIDEO_ID_PATTERN = r'(?P<edx_video_id>[-\w]+)'
3836

39-
# ADR 0028: ViewSets registered via DefaultRouter.
40-
router = DefaultRouter()
41-
router.register(r'home', HomeViewSet, basename='home')
42-
43-
urlpatterns = router.urls + [
44-
# DEPRECATED (ADR 0028): Use HomeViewSet instead (GET home/, home/courses/, home/libraries/).
45-
# Kept as backward-compatible aliases. Remove after one named release.
37+
urlpatterns = [
4638
path(
4739
'home',
4840
HomePageView.as_view(),

cms/djangoapps/contentstore/rest_api/v1/views/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"""
22
Views for v1 contentstore API.
33
"""
4-
54
from .certificates import CourseCertificatesView # noqa: F401
65
from .course_details import CourseDetailsView # noqa: F401
76
from .course_index import ContainerChildrenView, CourseIndexView # noqa: F401
@@ -11,7 +10,7 @@
1110
from .grading import CourseGradingView # noqa: F401
1211
from .group_configurations import CourseGroupConfigurationsView # noqa: F401
1312
from .help_urls import HelpUrlsView # noqa: F401
14-
from .home import HomePageCoursesView, HomePageLibrariesView, HomePageView, HomeViewSet # noqa: F401
13+
from .home import HomePageCoursesView, HomePageLibrariesView, HomePageView # noqa: F401
1514
from .proctoring import ProctoredExamSettingsView, ProctoringErrorsView # noqa: F401
1615
from .settings import CourseSettingsView # noqa: F401
1716
from .textbooks import CourseTextbooksView # noqa: F401

cms/djangoapps/contentstore/rest_api/v1/views/home.py

Lines changed: 2 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,7 @@
22

33
import edx_api_doc_tools as apidocs
44
from django.conf import settings
5-
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
6-
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
75
from organizations import api as org_api
8-
from rest_framework import viewsets
9-
from rest_framework.decorators import action
10-
from rest_framework.permissions import IsAuthenticated
116
from rest_framework.request import Request
127
from rest_framework.response import Response
138
from rest_framework.views import APIView
@@ -18,145 +13,6 @@
1813
from ..serializers import CourseHomeTabSerializer, LibraryTabSerializer, StudioHomeSerializer
1914

2015

21-
# ADR 0028 – consolidated from HomePageView, HomePageCoursesView, HomePageLibrariesView
22-
class HomeViewSet(viewsets.ViewSet):
23-
"""
24-
ViewSet for the Studio home page. Registered via DefaultRouter (basename ``home``).
25-
26-
Router-generated URLs:
27-
GET /api/contentstore/v1/home/ → list (aggregated home context)
28-
GET /api/contentstore/v1/home/courses/ → courses (course list only)
29-
GET /api/contentstore/v1/home/libraries/→ libraries (library list only)
30-
31-
ADR 0025 compliance notes:
32-
- Three different serializers are returned by ``get_serializer_class()`` depending
33-
on the action. ``serializer_class`` is set to the default (``StudioHomeSerializer``).
34-
- All response formatting is handled by the respective serializer class.
35-
"""
36-
37-
authentication_classes = (JwtAuthentication, SessionAuthenticationAllowInactiveUser)
38-
permission_classes = (IsAuthenticated,) # ADR 0026
39-
serializer_class = StudioHomeSerializer # default; overridden by get_serializer_class()
40-
41-
def get_serializer_class(self):
42-
"""Return the appropriate serializer class for the current action."""
43-
if self.action == 'courses':
44-
return CourseHomeTabSerializer
45-
if self.action == 'libraries':
46-
return LibraryTabSerializer
47-
return StudioHomeSerializer
48-
49-
def get_serializer(self, *args, **kwargs):
50-
"""Return a serializer instance using the action-appropriate class."""
51-
return self.get_serializer_class()(*args, **kwargs)
52-
53-
@apidocs.schema(
54-
parameters=[
55-
apidocs.string_parameter(
56-
"org",
57-
apidocs.ParameterLocation.QUERY,
58-
description="Query param to filter by course org",
59-
)],
60-
responses={
61-
200: StudioHomeSerializer,
62-
401: "The requester is not authenticated.",
63-
},
64-
)
65-
def list(self, request: Request):
66-
"""
67-
Get an object containing all courses and libraries on home page.
68-
69-
**Example Request**
70-
71-
GET /api/contentstore/v1/home/
72-
"""
73-
home_context = get_home_context(request, True)
74-
home_context.update({
75-
# 'allow_to_create_new_org' is actually about auto-creating organizations
76-
# (e.g. when creating a course or library), so we add an additional test.
77-
'allow_to_create_new_org': (
78-
home_context['can_create_organizations'] and
79-
org_api.is_autocreate_enabled()
80-
),
81-
'studio_name': settings.STUDIO_NAME,
82-
'studio_short_name': settings.STUDIO_SHORT_NAME,
83-
'studio_request_email': settings.FEATURES.get('STUDIO_REQUEST_EMAIL', ''),
84-
'tech_support_email': settings.TECH_SUPPORT_EMAIL,
85-
'platform_name': settings.PLATFORM_NAME,
86-
'user_is_active': request.user.is_active,
87-
})
88-
serializer = self.get_serializer(home_context)
89-
return Response(serializer.data)
90-
91-
@apidocs.schema(
92-
parameters=[
93-
apidocs.string_parameter(
94-
"org",
95-
apidocs.ParameterLocation.QUERY,
96-
description="Query param to filter by course org",
97-
)],
98-
responses={
99-
200: CourseHomeTabSerializer,
100-
401: "The requester is not authenticated.",
101-
},
102-
)
103-
@action(detail=False, methods=['get'], url_path='courses', url_name='courses')
104-
def courses(self, request: Request):
105-
"""
106-
Get an object containing all courses.
107-
108-
**Example Request**
109-
110-
GET /api/contentstore/v1/home/courses/
111-
"""
112-
active_courses, archived_courses, in_process_course_actions = get_course_context(request)
113-
courses_context = {
114-
"courses": active_courses,
115-
"archived_courses": archived_courses,
116-
"in_process_course_actions": in_process_course_actions,
117-
}
118-
serializer = self.get_serializer(courses_context)
119-
return Response(serializer.data)
120-
121-
@apidocs.schema(
122-
parameters=[
123-
apidocs.string_parameter(
124-
"org",
125-
apidocs.ParameterLocation.QUERY,
126-
description="Query param to filter by course org",
127-
),
128-
apidocs.query_parameter(
129-
"is_migrated",
130-
bool,
131-
description=(
132-
"Query param to filter by migrated status of library."
133-
" If present (true or false), it will filter by migration status"
134-
" else it will return all legacy libraries."
135-
),
136-
)
137-
],
138-
responses={
139-
200: LibraryTabSerializer,
140-
401: "The requester is not authenticated.",
141-
},
142-
)
143-
@action(detail=False, methods=['get'], url_path='libraries', url_name='libraries')
144-
def libraries(self, request: Request):
145-
"""
146-
Get an object containing all libraries on home page.
147-
148-
**Example Request**
149-
150-
GET /api/contentstore/v1/home/libraries/
151-
"""
152-
library_context = get_library_context(request)
153-
serializer = self.get_serializer(library_context)
154-
return Response(serializer.data)
155-
156-
157-
# DEPRECATED (ADR 0028): Use HomeViewSet instead.
158-
# Will be removed after one named release.
159-
# Use GET home/, home/courses/, home/libraries/ instead.
16016
@view_auth_classes(is_authenticated=True)
16117
class HomePageView(APIView):
16218
"""
@@ -243,13 +99,11 @@ def get(self, request: Request):
24399
return Response(serializer.data)
244100

245101

102+
@view_auth_classes(is_authenticated=True)
246103
class HomePageCoursesView(APIView):
247104
"""
248105
View for getting all courses and libraries available to the logged in user.
249106
"""
250-
authentication_classes = (JwtAuthentication, SessionAuthenticationAllowInactiveUser)
251-
permission_classes = (IsAuthenticated,)
252-
serializer_class = CourseHomeTabSerializer
253107
@apidocs.schema(
254108
parameters=[
255109
apidocs.string_parameter(
@@ -316,7 +170,7 @@ def get(self, request: Request):
316170
"archived_courses": archived_courses,
317171
"in_process_course_actions": in_process_course_actions,
318172
}
319-
serializer = self.serializer_class(courses_context)
173+
serializer = CourseHomeTabSerializer(courses_context)
320174
return Response(serializer.data)
321175

322176

cms/djangoapps/contentstore/rest_api/v1/views/tests/test_home.py

Lines changed: 3 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,17 @@
77
import ddt
88
import pytz
99
from django.conf import settings
10-
from django.test import TestCase, override_settings
10+
from django.test import override_settings
1111
from django.urls import reverse
1212
from opaque_keys.edx.locator import LibraryLocatorV2
1313
from openedx_content import api as content_api
1414
from organizations.tests.factories import OrganizationFactory
1515
from rest_framework import status
16-
from rest_framework.test import APIClient
1716

1817
from cms.djangoapps.contentstore.tests.test_libraries import LibraryTestCase
1918
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
2019
from cms.djangoapps.modulestore_migrator import api as migrator_api
2120
from cms.djangoapps.modulestore_migrator.data import CompositionLevel, RepeatHandlingStrategy
22-
from common.djangoapps.student.tests.factories import UserFactory
2321
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
2422
from openedx.core.djangoapps.content_libraries import api as lib_api
2523

@@ -402,54 +400,5 @@ def test_home_page_libraries_response(self):
402400
],
403401
}
404402

405-
assert response.status_code == status.HTTP_200_OK
406-
assert response.json() == expected_response
407-
408-
409-
class HomePageCoursesViewPermissionsTest(TestCase):
410-
"""
411-
ADR 0026 – permission regression tests for HomePageCoursesView.
412-
413-
Verifies that the explicit permission_classes = (IsAuthenticated,) enforces
414-
the same access rules previously set by the @view_auth_classes(is_authenticated=True)
415-
decorator.
416-
"""
417-
418-
def setUp(self):
419-
super().setUp()
420-
self.client = APIClient()
421-
self.url = reverse("cms.djangoapps.contentstore:v1:courses")
422-
self.user = UserFactory.create()
423-
self.staff_user = UserFactory.create(is_staff=True)
424-
425-
def test_unauthenticated_request_returns_401(self):
426-
"""
427-
Unauthenticated request (no credentials) must be rejected with 401.
428-
429-
Before ADR 0026: enforced by @view_auth_classes(is_authenticated=True).
430-
After ADR 0026: enforced by permission_classes = (IsAuthenticated,).
431-
"""
432-
response = self.client.get(self.url)
433-
assert response.status_code == status.HTTP_401_UNAUTHORIZED
434-
435-
def test_authenticated_user_gets_200(self):
436-
"""
437-
Any authenticated user (not necessarily staff) must receive 200.
438-
439-
HomePageCoursesView only requires authentication — no staff role needed.
440-
The view returns an empty course list for users with no assigned courses.
441-
"""
442-
self.client.force_authenticate(user=self.user)
443-
response = self.client.get(self.url)
444-
assert response.status_code == status.HTTP_200_OK
445-
446-
def test_staff_user_gets_200(self):
447-
"""Staff user must also receive 200 (staff is a superset of authenticated)."""
448-
self.client.force_authenticate(user=self.staff_user)
449-
response = self.client.get(self.url)
450-
assert response.status_code == status.HTTP_200_OK
451-
452-
def test_post_by_unauthenticated_returns_401(self):
453-
"""Non-GET methods also enforce authentication — POST without credentials is 401."""
454-
response = self.client.post(self.url, data={})
455-
assert response.status_code == status.HTTP_401_UNAUTHORIZED
403+
self.assertEqual(response.status_code, status.HTTP_200_OK) # noqa: PT009
404+
self.assertDictEqual(expected_response, response.json()) # noqa: PT009

0 commit comments

Comments
 (0)