Skip to content
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

feat: [AXIMST-111] create API for course textbooks #2491

Merged
merged 2 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
ProctoringErrorsSerializer
)
from .settings import CourseSettingsSerializer
from .textbooks import CourseTextbooksSerializer
from .videos import (
CourseVideosSerializer,
VideoUploadSerializer,
Expand Down
32 changes: 32 additions & 0 deletions cms/djangoapps/contentstore/rest_api/v1/serializers/textbooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
API Serializers for textbooks page
"""

from rest_framework import serializers


class CourseTextbookChapterSerializer(serializers.Serializer):
"""
Serializer for representing textbook chapter.
"""

title = serializers.CharField()
url = serializers.CharField()


class CourseTextbookItemSerializer(serializers.Serializer):
"""
Serializer for representing textbook item.
"""

id = serializers.CharField()
chapters = CourseTextbookChapterSerializer(many=True)
tab_title = serializers.CharField()


class CourseTextbooksSerializer(serializers.Serializer):
"""
Serializer for representing course's textbooks.
"""

textbooks = serializers.ListField()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it should be

Suggested change
textbooks = serializers.ListField()
textbooks = CourseTextbookItemSerializer(many=True)

right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is possible
But I used the same format as i was in previous API's, dict instead of list

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this was intentional, idk

6 changes: 6 additions & 0 deletions cms/djangoapps/contentstore/rest_api/v1/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
CourseCertificatesView,
CourseDetailsView,
CourseTeamView,
CourseTextbooksView,
CourseIndexView,
CourseGradingView,
CourseRerunView,
Expand Down Expand Up @@ -109,6 +110,11 @@
CourseCertificatesView.as_view(),
name="certificates"
),
re_path(
fr'^textbooks/{COURSE_ID_PATTERN}$',
CourseTextbooksView.as_view(),
name="textbooks"
),
re_path(
fr'^container_handler/{settings.USAGE_KEY_PATTERN}$',
ContainerHandlerView.as_view(),
Expand Down
1 change: 1 addition & 0 deletions cms/djangoapps/contentstore/rest_api/v1/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .proctoring import ProctoredExamSettingsView, ProctoringErrorsView
from .home import HomePageView, HomePageCoursesView, HomePageLibrariesView
from .settings import CourseSettingsView
from .textbooks import CourseTextbooksView
from .videos import (
CourseVideosView,
VideoUsageView,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""
Unit tests for the course's textbooks.
"""
from django.urls import reverse
from rest_framework import status

from cms.djangoapps.contentstore.tests.utils import CourseTestCase

from ...mixins import PermissionAccessMixin


class CourseTextbooksViewTest(CourseTestCase, PermissionAccessMixin):
"""
Tests for CourseTextbooksView.
"""

def setUp(self):
super().setUp()
self.url = reverse(
"cms.djangoapps.contentstore:v1:textbooks",
kwargs={"course_id": self.course.id},
)

def test_success_response(self):
"""
Check that endpoint is valid and success response.
"""
expected_textbook = [
{
"tab_title": "Textbook Name",
"chapters": [
{"title": "Chapter 1", "url": "/static/book.pdf"},
{"title": "Chapter 2", "url": "/static/story.pdf"},
],
"id": "Textbook_Name",
}
]
self.course.pdf_textbooks = expected_textbook
self.save_course()

response = self.client.get(self.url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["textbooks"], expected_textbook)
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def test_not_valid_usage_key_string(self):
)
url = self.get_reverse_url(usage_key_string)
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)


class ContainerVerticalViewTest(BaseXBlockContainer):
Expand Down Expand Up @@ -177,4 +177,4 @@ def test_not_valid_usage_key_string(self):
)
url = self.get_reverse_url(usage_key_string)
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
90 changes: 90 additions & 0 deletions cms/djangoapps/contentstore/rest_api/v1/views/textbooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
""" API Views for course textbooks """

import edx_api_doc_tools as apidocs
from opaque_keys.edx.keys import CourseKey
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView

from cms.djangoapps.contentstore.utils import get_textbooks_context
from cms.djangoapps.contentstore.rest_api.v1.serializers import (
CourseTextbooksSerializer,
)
from common.djangoapps.student.auth import has_studio_read_access
from openedx.core.lib.api.view_utils import (
DeveloperErrorViewMixin,
verify_course_exists,
view_auth_classes,
)
from xmodule.modulestore.django import modulestore


@view_auth_classes(is_authenticated=True)
class CourseTextbooksView(DeveloperErrorViewMixin, APIView):
"""
View for course textbooks page.
"""

@apidocs.schema(
parameters=[
apidocs.string_parameter(
"course_id", apidocs.ParameterLocation.PATH, description="Course ID"
),
],
responses={
200: CourseTextbooksSerializer,
401: "The requester is not authenticated.",
403: "The requester cannot access the specified course.",
404: "The requested course does not exist.",
},
)
@verify_course_exists()
def get(self, request: Request, course_id: str):
"""
Get an object containing course's textbooks.

**Example Request**

GET /api/contentstore/v1/textbooks/{course_id}

**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 textbooks.

**Example Response**

```json
{
"textbooks": [
{
"tab_title": "Textbook Name",
"chapters": [
{
"title": "Chapter 1",
"url": "/static/Present_Perfect.pdf"
},
{
"title": "Chapter 2",
"url": "/static/Lear.pdf"
}
],
"id": "Textbook_Name"
}
]
}
```
"""
course_key = CourseKey.from_string(course_id)
store = modulestore()

if not has_studio_read_access(request.user, course_key):
self.permission_denied(request)

with store.bulk_operations(course_key):
course = modulestore().get_course(course_key)
textbooks_context = get_textbooks_context(course)
serializer = CourseTextbooksSerializer(textbooks_context)
return Response(serializer.data)
16 changes: 16 additions & 0 deletions cms/djangoapps/contentstore/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1958,6 +1958,22 @@ def get_certificates_context(course, user):
return context


def get_textbooks_context(course):
"""
Utils is used to get context for textbooks for course.
It is used for both DRF and django views.
"""

upload_asset_url = reverse_course_url('assets_handler', course.id)
textbook_url = reverse_course_url('textbooks_list_handler', course.id)
return {
'context_course': course,
'textbooks': course.pdf_textbooks,
'upload_asset_url': upload_asset_url,
'textbook_url': textbook_url,
}


class StudioPermissionsService:
"""
Service that can provide information about a user's permissions.
Expand Down
11 changes: 3 additions & 8 deletions cms/djangoapps/contentstore/views/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
get_grading_url,
get_schedule_details_url,
get_course_rerun_context,
get_textbooks_context,
initialize_permissions,
remove_all_instructors,
reverse_course_url,
Expand Down Expand Up @@ -1347,14 +1348,8 @@ def textbooks_list_handler(request, course_key_string):

if "application/json" not in request.META.get('HTTP_ACCEPT', 'text/html'):
# return HTML page
upload_asset_url = reverse_course_url('assets_handler', course_key)
textbook_url = reverse_course_url('textbooks_list_handler', course_key)
return render_to_response('textbooks.html', {
'context_course': course,
'textbooks': course.pdf_textbooks,
'upload_asset_url': upload_asset_url,
'textbook_url': textbook_url,
})
textbooks_context = get_textbooks_context(course)
return render_to_response('textbooks.html', textbooks_context)

# from here on down, we know the client has requested JSON
if request.method == 'GET':
Expand Down
Loading