Merge pull request #23956 from edx/dcs/resume-api
Added courseware API for retrieving the last completed block
This commit is contained in:
@@ -6,6 +6,7 @@ from datetime import datetime
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from completion.test_utils import CompletionWaffleTestMixin, submit_completions_for_testing
|
||||
from django.conf import settings
|
||||
|
||||
from lms.djangoapps.courseware.access_utils import ACCESS_DENIED, ACCESS_GRANTED
|
||||
@@ -35,6 +36,9 @@ class BaseCoursewareTests(SharedModuleStoreTestCase):
|
||||
emit_signals=True,
|
||||
modulestore=cls.store,
|
||||
)
|
||||
cls.chapter = ItemFactory(parent=cls.course, category='chapter')
|
||||
cls.sequence = ItemFactory(parent=cls.chapter, category='sequential', display_name='sequence')
|
||||
cls.unit = ItemFactory.create(parent=cls.sequence, category='vertical', display_name="Vertical")
|
||||
|
||||
cls.user = UserFactory(
|
||||
username='student',
|
||||
@@ -114,9 +118,6 @@ class SequenceApiTestViews(BaseCoursewareTests):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
chapter = ItemFactory(parent=cls.course, category='chapter')
|
||||
cls.sequence = ItemFactory(parent=chapter, category='sequential', display_name='sequence')
|
||||
ItemFactory.create(parent=cls.sequence, category='vertical', display_name="Vertical")
|
||||
cls.url = '/api/courseware/sequence/{}'.format(cls.sequence.location)
|
||||
|
||||
@classmethod
|
||||
@@ -125,9 +126,33 @@ class SequenceApiTestViews(BaseCoursewareTests):
|
||||
super().tearDownClass()
|
||||
|
||||
def test_sequence_metadata(self):
|
||||
print(self.url)
|
||||
print(self.course.location)
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
assert response.data['display_name'] == 'sequence'
|
||||
assert len(response.data['items']) == 1
|
||||
|
||||
|
||||
class ResumeApiTestViews(BaseCoursewareTests, CompletionWaffleTestMixin):
|
||||
"""
|
||||
Tests for the resume API
|
||||
"""
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.url = '/api/courseware/resume/{}'.format(cls.course.id)
|
||||
|
||||
def test_resume_no_completion(self):
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
assert response.data['block_id'] is None
|
||||
assert response.data['unit_id'] is None
|
||||
assert response.data['section_id'] is None
|
||||
|
||||
def test_resume_with_completion(self):
|
||||
self.override_waffle_switch(True)
|
||||
submit_completions_for_testing(self.user, [self.unit.location])
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
assert response.data['block_id'] == str(self.unit.location)
|
||||
assert response.data['unit_id'] == str(self.unit.location)
|
||||
assert response.data['section_id'] == str(self.sequence.location)
|
||||
|
||||
@@ -15,4 +15,7 @@ urlpatterns = [
|
||||
url(r'^sequence/{}'.format(settings.USAGE_KEY_PATTERN),
|
||||
views.SequenceMetadata.as_view(),
|
||||
name="sequence-api"),
|
||||
url(r'^resume/{}'.format(settings.COURSE_KEY_PATTERN),
|
||||
views.Resume.as_view(),
|
||||
name="resume-api"),
|
||||
]
|
||||
|
||||
@@ -5,16 +5,19 @@ Course API Views
|
||||
import json
|
||||
|
||||
from babel.numbers import get_currency_symbol
|
||||
from completion.exceptions import UnavailableCompletionData
|
||||
from completion.utilities import get_key_to_last_completed_block
|
||||
from django.urls import reverse
|
||||
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
|
||||
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
|
||||
from opaque_keys.edx.keys import CourseKey, UsageKey
|
||||
from rest_framework.generics import RetrieveAPIView
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from edxnotes.helpers import is_feature_enabled
|
||||
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
|
||||
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
|
||||
from lms.djangoapps.course_api.api import course_detail
|
||||
from lms.djangoapps.courseware.access import has_access
|
||||
from lms.djangoapps.courseware.courses import check_course_access
|
||||
@@ -26,6 +29,8 @@ from openedx.features.content_type_gating.models import ContentTypeGatingConfig
|
||||
from openedx.features.course_duration_limits.access import generate_course_expired_message
|
||||
from openedx.features.discounts.utils import generate_offer_html
|
||||
from student.models import CourseEnrollment
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.search import path_to_location
|
||||
|
||||
from .serializers import CourseInfoSerializer
|
||||
|
||||
@@ -268,3 +273,61 @@ class SequenceMetadata(DeveloperErrorViewMixin, APIView):
|
||||
str(usage_key),
|
||||
disable_staff_debug_info=True)
|
||||
return Response(json.loads(sequence.handle_ajax('metadata', None)))
|
||||
|
||||
|
||||
class Resume(DeveloperErrorViewMixin, APIView):
|
||||
"""
|
||||
**Use Cases**
|
||||
|
||||
Request the last completed block in a course
|
||||
|
||||
**Example Requests**
|
||||
|
||||
GET /api/courseware/resume/{course_key}
|
||||
|
||||
**Response Values**
|
||||
|
||||
Body consists of the following fields:
|
||||
|
||||
* block: the last completed block key
|
||||
* section: the key to the section
|
||||
* unit: the key to the unit
|
||||
If no completion data is available, the keys will be null
|
||||
|
||||
**Returns**
|
||||
|
||||
* 200 on success with above fields.
|
||||
* 400 if an invalid parameter was sent.
|
||||
* 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.
|
||||
"""
|
||||
|
||||
authentication_classes = (
|
||||
JwtAuthentication,
|
||||
SessionAuthenticationAllowInactiveUser,
|
||||
)
|
||||
permission_classes = (IsAuthenticated, )
|
||||
|
||||
def get(self, request, course_key_string, *args, **kwargs):
|
||||
"""
|
||||
Return response to a GET request.
|
||||
"""
|
||||
course_id = CourseKey.from_string(course_key_string)
|
||||
resp = {
|
||||
'block_id': None,
|
||||
'section_id': None,
|
||||
'unit_id': None,
|
||||
}
|
||||
|
||||
try:
|
||||
block_key = get_key_to_last_completed_block(request.user, course_id)
|
||||
path = path_to_location(modulestore(), block_key, request, full_path=True)
|
||||
resp['section_id'] = str(path[2])
|
||||
resp['unit_id'] = str(path[3])
|
||||
resp['block_id'] = str(block_key)
|
||||
|
||||
except UnavailableCompletionData:
|
||||
pass
|
||||
|
||||
return Response(resp)
|
||||
|
||||
Reference in New Issue
Block a user