feat: [AXIM-26] Extended BlocksInCourseView API

This commit is contained in:
KyryloKireiev
2023-09-15 15:20:18 +03:00
committed by Glib Glugovskiy
parent 9c48e38f9c
commit c4fe8d30e3
3 changed files with 169 additions and 1 deletions

View File

@@ -15,6 +15,7 @@ from common.djangoapps.student.models import CourseEnrollment # pylint: disable
from common.djangoapps.student.tests.factories import UserFactory # pylint: disable=unused-import
from lms.djangoapps.mobile_api.testutils import MobileAPITestCase, MobileAuthTestMixin, MobileCourseAccessTestMixin
from lms.djangoapps.mobile_api.utils import API_V1, API_V05
from lms.djangoapps.course_api.blocks.tests.test_views import TestBlocksInCourseView
from openedx.features.course_experience import ENABLE_COURSE_GOALS
from xmodule.html_block import CourseInfoBlock # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order
@@ -255,3 +256,49 @@ class TestCourseGoalsUserActivityAPI(MobileAPITestCase, SharedModuleStoreTestCas
'For this mobile request, user activity is not enabled for this user {} and course {}'.format(
str(self.user.id), str(self.course.id))
)
@ddt.ddt
class TestBlocksInfoInCourseView(TestBlocksInCourseView): # lint-amnesty, pylint: disable=test-inherits-tests
"""
Test class for BlocksInfoInCourseView
"""
def setUp(self):
super().setUp()
self.url = reverse('blocks_info_in_course', kwargs={
'api_version': 'v3',
})
@patch('lms.djangoapps.mobile_api.course_info.views.certificate_downloadable_status')
def test_additional_info_response(self, mock_certificate_downloadable_status):
certificate_url = 'https://test_certificate_url'
mock_certificate_downloadable_status.return_value = {
'is_downloadable': True,
'download_url': certificate_url,
}
expected_image_urls = {
'image':
{
'large': '/asset-v1:edX+toy+2012_Fall+type@asset+block@just_a_test.jpg',
'raw': '/asset-v1:edX+toy+2012_Fall+type@asset+block@just_a_test.jpg',
'small': '/asset-v1:edX+toy+2012_Fall+type@asset+block@just_a_test.jpg'
}
}
response = self.verify_response(url=self.url)
assert response.status_code == 200
assert response.data['id'] == str(self.course.id)
assert response.data['name'] == self.course.display_name
assert response.data['number'] == self.course.display_number_with_default
assert response.data['org'] == self.course.display_org_with_default
assert response.data['start'] == self.course.start
assert response.data['start_display'] == 'July 17, 2015'
assert response.data['start_type'] == 'timestamp'
assert response.data['end'] == self.course.end
assert response.data['media'] == expected_image_urls
assert response.data['certificate'] == {'url': certificate_url}
assert response.data['is_self_paced'] is False
mock_certificate_downloadable_status.assert_called_once()

View File

@@ -6,7 +6,7 @@ URLs for course_info API
from django.conf import settings
from django.urls import path, re_path
from .views import CourseHandoutsList, CourseUpdatesList, CourseGoalsRecordUserActivity
from .views import CourseHandoutsList, CourseUpdatesList, CourseGoalsRecordUserActivity, BlocksInfoInCourseView
urlpatterns = [
re_path(
@@ -20,4 +20,5 @@ urlpatterns = [
name='course-updates-list'
),
path('record_user_activity', CourseGoalsRecordUserActivity.as_view(), name='record_user_activity'),
path('blocks/', BlocksInfoInCourseView.as_view(), name="blocks_info_in_course"),
]

View File

@@ -12,8 +12,12 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from common.djangoapps.static_replace import make_static_urls_absolute
from lms.djangoapps.certificates.api import certificate_downloadable_status
from lms.djangoapps.courseware.courses import get_course_info_section_block
from lms.djangoapps.course_goals.models import UserActivity
from lms.djangoapps.course_api.blocks.views import BlocksInCourseView
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.lib.api.view_utils import view_auth_classes
from openedx.core.lib.xblock_utils import get_course_update_items
from openedx.features.course_experience import ENABLE_COURSE_GOALS
from ..decorators import mobile_course_access, mobile_view
@@ -166,3 +170,119 @@ class CourseGoalsRecordUserActivity(APIView):
# Populate user activity for tracking progress towards a user's course goals
UserActivity.record_user_activity(user, course_key)
return Response(status=(200))
@view_auth_classes(is_authenticated=False)
class BlocksInfoInCourseView(BlocksInCourseView):
"""
**Use Case**
Returns the blocks in the course according to the requesting user's access level.
Add to response info fields with information about course
**Example requests**:
This api works with all versions {api_version}, you can use: v0.5, v1, v2 or v3
GET /api/mobile/{api_version}/course_info/blocks/?course_id=<course_id>
GET /api/mobile/{api_version}/course_info/blocks/?course_id=<course_id>
&username=anjali
&depth=all
&requested_fields=graded,format,student_view_multi_device,lti_url
&block_counts=video
&student_view_data=video
&block_types_filter=problem,html
**Response example**
Body consists of the following fields:
root: (str) The ID of the root node of the requested course block structure.\
blocks: (dict) A dictionary or list, based on the value of the
"return_type" parameter. Maps block usage IDs to a collection of
information about each block. Each block contains the following
fields.
id: (str) The Course's id (Course Run key)
name: (str) The course's name
number: (str) The course's number
org: (str) The course's organisation
start: (str) Date the course begins, in ISO 8601 notation
start_display: (str) Readably formatted start of the course
start_type: (str) Hint describing how `start_display` is set. One of:
* `"string"`: manually set by the course author
* `"timestamp"`: generated from the `start` timestamp
* `"empty"`: no start date is specified
end: (str) Date the course ends, in ISO 8601 notation
media: (dict) An object that contains named media items. Included here:
* course_image: An image to show for the course. Represented
as an object with the following fields:
* uri: The location of the image
certificate: (dict) Information about the user's earned certificate in the course.
Included here:
* uri: The location of the user's certificate
is_self_paced: (bool) Indicates if the course is self paced
**Returns**
* 200 on success with above fields.
* 400 if an invalid parameter was sent or the username was not provided
* 401 unauthorized, the provided access token has expired and is no longer valid
for an authenticated request.
* 403 if a user who does not have permission to masquerade as
another user specifies a username other than their own.
* 404 if the course is not available or cannot be seen.
"""
def get_certificate(self, request, course_id):
"""Returns the information about the user's certificate in the course."""
if request.user.is_authenticated:
certificate_info = certificate_downloadable_status(request.user, course_id)
if certificate_info['is_downloadable']:
return {
'url': request.build_absolute_uri(
certificate_info['download_url']
),
}
return {}
# pylint: disable=arguments-differ
def list(self, request, **kwargs):
"""
REST API endpoint for listing all the blocks information in the course and
information about the course while regarding user access and roles.
Arguments:
request - Django request object
"""
response = super().list(request, kwargs)
if request.GET.get('return_type', 'dict') == 'dict':
course_id = request.query_params.get('course_id', None)
course_key = CourseKey.from_string(course_id)
course_overview = CourseOverview.get_from_id(course_key)
course_data = {
# identifiers
'id': course_id,
'name': course_overview.display_name,
'number': course_overview.display_number_with_default,
'org': course_overview.display_org_with_default,
# dates
'start': course_overview.start,
'start_display': course_overview.start_display,
'start_type': course_overview.start_type,
'end': course_overview.end,
# various URLs
'media': {
'image': course_overview.image_urls,
},
'certificate': self.get_certificate(request, course_key),
'is_self_paced': course_overview.self_paced
}
response.data.update(course_data)
return response