Skip to content
Snippets Groups Projects
Commit 687d2233 authored by Dave St.Germain's avatar Dave St.Germain
Browse files

Added a courseware API endpoint to return the last completed block/unit/section.

parent fe2abafc
No related merge requests found
......@@ -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)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment