feat: [AXIM-26] Extended BlocksInCourseView API
This commit is contained in:
committed by
Glib Glugovskiy
parent
9c48e38f9c
commit
c4fe8d30e3
@@ -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()
|
||||
|
||||
@@ -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"),
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user