diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_mongo_call_count.py b/common/lib/xmodule/xmodule/modulestore/tests/test_mongo_call_count.py index 7f9f9ab6a0..dccdc1a304 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_mongo_call_count.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_mongo_call_count.py @@ -164,8 +164,6 @@ class CountMongoCallsCourseTraversal(TestCase): with store_builder.build(request_cache=request_cache) as (content_store, modulestore): course_key = self._import_course(content_store, modulestore) - # Course traversal modeled after the traversal done here: - # lms/djangoapps/mobile_api/video_outlines/serializers.py:BlockOutline # Starting at the root course block, do a breadth-first traversal using # get_children() to retrieve each block's children. with check_mongo_calls(num_mongo_calls): diff --git a/lms/djangoapps/mobile_api/urls.py b/lms/djangoapps/mobile_api/urls.py index 407d4f321d..8d7984532f 100644 --- a/lms/djangoapps/mobile_api/urls.py +++ b/lms/djangoapps/mobile_api/urls.py @@ -9,6 +9,5 @@ from .users.views import my_user_info urlpatterns = [ url(r'^users/', include('mobile_api.users.urls')), url(r'^my_user_info', my_user_info, name='user-info'), - url(r'^video_outlines/', include('mobile_api.video_outlines.urls')), url(r'^course_info/', include('mobile_api.course_info.urls')), ] diff --git a/lms/djangoapps/mobile_api/users/serializers.py b/lms/djangoapps/mobile_api/users/serializers.py index 595d83c529..e6814350e0 100644 --- a/lms/djangoapps/mobile_api/users/serializers.py +++ b/lms/djangoapps/mobile_api/users/serializers.py @@ -73,11 +73,10 @@ class CourseOverviewField(serializers.RelatedField): request=request, ) if course_overview.is_discussion_tab_enabled() else None, - 'video_outline': reverse( - 'video-summary-list', - kwargs={'api_version': api_version, 'course_id': course_id}, - request=request, - ), + # This is an old API that was removed as part of DEPR-4. We keep the + # field present in case API parsers expect it, but this API is now + # removed. + 'video_outline': None, } diff --git a/lms/djangoapps/mobile_api/users/tests.py b/lms/djangoapps/mobile_api/users/tests.py index bb27e2780f..4a24f34ecd 100644 --- a/lms/djangoapps/mobile_api/users/tests.py +++ b/lms/djangoapps/mobile_api/users/tests.py @@ -111,7 +111,6 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest self.assertIn('courses/{}/about'.format(self.course.id), found_course['course_about']) self.assertIn('course_info/{}/updates'.format(self.course.id), found_course['course_updates']) self.assertIn('course_info/{}/handouts'.format(self.course.id), found_course['course_handouts']) - self.assertIn('video_outlines/courses/{}'.format(self.course.id), found_course['video_outline']) self.assertEqual(found_course['id'], unicode(self.course.id)) self.assertEqual(courses[0]['mode'], CourseMode.DEFAULT_MODE_SLUG) self.assertEqual(courses[0]['course']['subscription_id'], self.course.clean_id(padding_char='_')) diff --git a/lms/djangoapps/mobile_api/video_outlines/__init__.py b/lms/djangoapps/mobile_api/video_outlines/__init__.py deleted file mode 100644 index eff642ef52..0000000000 --- a/lms/djangoapps/mobile_api/video_outlines/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Video outline API -""" diff --git a/lms/djangoapps/mobile_api/video_outlines/models.py b/lms/djangoapps/mobile_api/video_outlines/models.py deleted file mode 100644 index d2e8572729..0000000000 --- a/lms/djangoapps/mobile_api/video_outlines/models.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -A models.py is required to make this an app (until we move to Django 1.7) -""" diff --git a/lms/djangoapps/mobile_api/video_outlines/serializers.py b/lms/djangoapps/mobile_api/video_outlines/serializers.py deleted file mode 100644 index 5737df6b60..0000000000 --- a/lms/djangoapps/mobile_api/video_outlines/serializers.py +++ /dev/null @@ -1,253 +0,0 @@ -""" -Serializer for video outline -""" -from edxval.api import ValInternalError, get_video_info_for_course_and_profiles -from rest_framework.reverse import reverse - -from courseware.access import has_access -from courseware.courses import get_course_by_id -from courseware.model_data import FieldDataCache -from courseware.module_render import get_module_for_descriptor -from util.module_utils import get_dynamic_descriptor_children -from xmodule.modulestore.django import modulestore -from xmodule.modulestore.mongo.base import BLOCK_TYPES_WITH_CHILDREN - - -class BlockOutline(object): - """ - Serializes course videos, pulling data from VAL and the video modules. - """ - def __init__(self, course_id, start_block, block_types, request, video_profiles, api_version): - """Create a BlockOutline using `start_block` as a starting point.""" - self.start_block = start_block - self.block_types = block_types - self.course_id = course_id - self.api_version = api_version - self.request = request # needed for making full URLS - self.local_cache = {} - try: - self.local_cache['course_videos'] = get_video_info_for_course_and_profiles( - unicode(course_id), video_profiles - ) - except ValInternalError: # pragma: nocover - self.local_cache['course_videos'] = {} - - def __iter__(self): - def parent_or_requested_block_type(usage_key): - """ - Returns whether the usage_key's block_type is one of self.block_types or a parent type. - """ - return ( - usage_key.block_type in self.block_types or - usage_key.block_type in BLOCK_TYPES_WITH_CHILDREN - ) - - def create_module(descriptor): - """ - Factory method for creating and binding a module for the given descriptor. - """ - field_data_cache = FieldDataCache.cache_for_descriptor_descendents( - self.course_id, self.request.user, descriptor, depth=0, - ) - course = get_course_by_id(self.course_id) - return get_module_for_descriptor( - self.request.user, self.request, descriptor, field_data_cache, self.course_id, course=course - ) - - with modulestore().bulk_operations(self.course_id): - child_to_parent = {} - stack = [self.start_block] - while stack: - curr_block = stack.pop() - - if curr_block.hide_from_toc: - # For now, if the 'hide_from_toc' setting is set on the block, do not traverse down - # the hierarchy. The reason being is that these blocks may not have human-readable names - # to display on the mobile clients. - # Eventually, we'll need to figure out how we want these blocks to be displayed on the - # mobile clients. As they are still accessible in the browser, just not navigatable - # from the table-of-contents. - continue - - if curr_block.location.block_type in self.block_types: - if not has_access(self.request.user, 'load', curr_block, course_key=self.course_id): - continue - - summary_fn = self.block_types[curr_block.category] - block_path = list(path(curr_block, child_to_parent, self.start_block)) - unit_url, section_url = find_urls(self.course_id, curr_block, child_to_parent, self.request) - - yield { - "path": block_path, - "named_path": [b["name"] for b in block_path], - "unit_url": unit_url, - "section_url": section_url, - "summary": summary_fn( - self.course_id, - curr_block, - self.request, - self.local_cache, - self.api_version - ) - } - - if curr_block.has_children: - children = get_dynamic_descriptor_children( - curr_block, - self.request.user.id, - create_module, - usage_key_filter=parent_or_requested_block_type - ) - for block in reversed(children): - stack.append(block) - child_to_parent[block] = curr_block - - -def path(block, child_to_parent, start_block): - """path for block""" - block_path = [] - while block in child_to_parent: - block = child_to_parent[block] - if block is not start_block: - block_path.append({ - # to be consistent with other edx-platform clients, return the defaulted display name - 'name': block.display_name_with_default_escaped, # xss-lint: disable=python-deprecated-display-name - 'category': block.category, - 'id': unicode(block.location) - }) - return reversed(block_path) - - -def find_urls(course_id, block, child_to_parent, request): - """ - Find the section and unit urls for a block. - - Returns: - unit_url, section_url: - unit_url (str): The url of a unit - section_url (str): The url of a section - - """ - block_path = [] - while block in child_to_parent: - block = child_to_parent[block] - block_path.append(block) - - block_list = list(reversed(block_path)) - block_count = len(block_list) - - chapter_id = block_list[1].location.block_id if block_count > 1 else None - section = block_list[2] if block_count > 2 else None - position = None - - if block_count > 3: - position = 1 - for block in section.children: - if block.block_id == block_list[3].url_name: - break - position += 1 - - kwargs = {'course_id': unicode(course_id)} - if chapter_id is None: - course_url = reverse("courseware", kwargs=kwargs, request=request) - return course_url, course_url - - kwargs['chapter'] = chapter_id - if section is None: - chapter_url = reverse("courseware_chapter", kwargs=kwargs, request=request) - return chapter_url, chapter_url - - kwargs['section'] = section.url_name - section_url = reverse("courseware_section", kwargs=kwargs, request=request) - if position is None: - return section_url, section_url - - kwargs['position'] = position - unit_url = reverse("courseware_position", kwargs=kwargs, request=request) - return unit_url, section_url - - -def video_summary(video_profiles, course_id, video_descriptor, request, local_cache, api_version): - """ - returns summary dict for the given video module - """ - always_available_data = { - "name": video_descriptor.display_name, - "category": video_descriptor.category, - "id": unicode(video_descriptor.scope_ids.usage_id), - "only_on_web": video_descriptor.only_on_web, - } - - all_sources = [] - - if video_descriptor.only_on_web: - ret = { - "video_url": None, - "video_thumbnail_url": None, - "duration": 0, - "size": 0, - "transcripts": {}, - "language": None, - "all_sources": all_sources, - } - ret.update(always_available_data) - return ret - - # Get encoded videos - video_data = local_cache['course_videos'].get(video_descriptor.edx_video_id, {}) - - # Get highest priority video to populate backwards compatible field - default_encoded_video = {} - - if video_data: - for profile in video_profiles: - default_encoded_video = video_data['profiles'].get(profile, {}) - if default_encoded_video: - break - - if default_encoded_video: - video_url = default_encoded_video['url'] - # Then fall back to VideoDescriptor fields for video URLs - elif video_descriptor.html5_sources: - video_url = video_descriptor.html5_sources[0] - all_sources = video_descriptor.html5_sources - else: - video_url = video_descriptor.source - - if video_descriptor.source: - all_sources.append(video_descriptor.source) - - # Get duration/size, else default - duration = video_data.get('duration', None) - size = default_encoded_video.get('file_size', 0) - - # Transcripts... - transcripts_info = video_descriptor.get_transcripts_info() - transcript_langs = video_descriptor.available_translations(transcripts=transcripts_info) - - transcripts = { - lang: reverse( - 'video-transcripts-detail', - kwargs={ - 'course_id': unicode(course_id), - 'block_id': video_descriptor.scope_ids.usage_id.block_id, - 'lang': lang, - 'api_version': api_version - }, - request=request, - ) - for lang in transcript_langs - } - - ret = { - "video_url": video_url, - "video_thumbnail_url": None, - "duration": duration, - "size": size, - "transcripts": transcripts, - "language": video_descriptor.get_default_transcript_language(transcripts_info), - "encoded_videos": video_data.get('profiles'), - "all_sources": all_sources, - } - ret.update(always_available_data) - return ret diff --git a/lms/djangoapps/mobile_api/video_outlines/tests.py b/lms/djangoapps/mobile_api/video_outlines/tests.py deleted file mode 100644 index 7158019e75..0000000000 --- a/lms/djangoapps/mobile_api/video_outlines/tests.py +++ /dev/null @@ -1,1041 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Tests for video outline API -""" -import ddt -import itertools -import json - -from collections import namedtuple -from mock import Mock -from uuid import uuid4 - -from django.conf import settings -from edxval import api -from milestones.tests.utils import MilestonesTestCaseMixin -from mock import patch - -from mobile_api.models import MobileApiConfig -from mobile_api.testutils import ( - MobileAPITestCase, - MobileAuthTestMixin, - MobileCourseAccessTestMixin -) -from mobile_api.utils import API_V05, API_V1 -from openedx.core.djangoapps.course_groups.cohorts import add_user_to_cohort, remove_user_from_cohort -from openedx.core.djangoapps.course_groups.models import CourseUserGroupPartitionGroup -from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory -from xmodule.modulestore.django import modulestore -from xmodule.modulestore.tests.factories import ItemFactory -from xmodule.partitions.partitions import Group, UserPartition -from xmodule.video_module import transcripts_utils - - -class TestVideoAPITestCase(MobileAPITestCase): - """ - Base test class for video related mobile APIs - """ - def setUp(self): - super(TestVideoAPITestCase, self).setUp() - self.section = ItemFactory.create( - parent=self.course, - category="chapter", - display_name=u"test factory section omega \u03a9", - ) - self.sub_section = ItemFactory.create( - parent=self.section, - category="sequential", - display_name=u"test subsection omega \u03a9", - ) - - self.unit = ItemFactory.create( - parent=self.sub_section, - category="vertical", - metadata={'graded': True, 'format': 'Homework'}, - display_name=u"test unit omega \u03a9", - ) - self.other_unit = ItemFactory.create( - parent=self.sub_section, - category="vertical", - metadata={'graded': True, 'format': 'Homework'}, - display_name=u"test unit omega 2 \u03a9", - ) - self.nameless_unit = ItemFactory.create( - parent=self.sub_section, - category="vertical", - metadata={'graded': True, 'format': 'Homework'}, - display_name=None, - ) - - self.edx_video_id = 'testing-123' - self.video_url = 'http://val.edx.org/val/video.mp4' - self.video_url_high = 'http://val.edx.org/val/video_high.mp4' - self.video_url_low = 'http://val.edx.org/val/video_low.mp4' - self.youtube_url = 'http://val.edx.org/val/youtube.mp4' - self.html5_video_url = 'http://video.edx.org/html5/video.mp4' - - api.create_profile('youtube') - api.create_profile('mobile_high') - api.create_profile('mobile_low') - - # create the video in VAL - api.create_video({ - 'edx_video_id': self.edx_video_id, - 'status': 'test', - 'client_video_id': u"test video omega \u03a9", - 'duration': 12, - 'courses': [unicode(self.course.id)], - 'encoded_videos': [ - { - 'profile': 'youtube', - 'url': 'xyz123', - 'file_size': 0, - 'bitrate': 1500 - }, - { - 'profile': 'mobile_low', - 'url': self.video_url, - 'file_size': 12345, - 'bitrate': 250 - }, - { - 'profile': 'mobile_high', - 'url': self.video_url_high, - 'file_size': 99999, - 'bitrate': 250 - }, - - ]}) - - # Set requested profiles - MobileApiConfig(video_profiles="mobile_low,mobile_high,youtube").save() - - -class TestVideoAPIMixin(object): - """ - Mixin class that provides helpers for testing video related mobile APIs - """ - def _create_video_with_subs(self, custom_subid=None): - """ - Creates and returns a video with stored subtitles. - """ - subid = custom_subid or uuid4().hex - transcripts_utils.save_subs_to_store( - { - 'start': [100, 200, 240, 390, 1000], - 'end': [200, 240, 380, 1000, 1500], - 'text': [ - 'subs #1', - 'subs #2', - 'subs #3', - 'subs #4', - 'subs #5' - ] - }, - subid, - self.course) - return ItemFactory.create( - parent=self.unit, - category="video", - edx_video_id=self.edx_video_id, - display_name=u"test video omega \u03a9", - sub=subid - ) - - def _verify_paths(self, course_outline, path_list, outline_index=0): - """ - Takes a path_list and compares it against the course_outline - - Attributes: - course_outline (list): A list of dictionaries that includes a 'path' - and 'named_path' field which we will be comparing path_list to - path_list (list): A list of the expected strings - outline_index (int): Index into the course_outline list for which the - path is being tested. - """ - path = course_outline[outline_index]['path'] - self.assertEqual(len(path), len(path_list)) - for i in range(len(path_list)): - self.assertEqual(path_list[i], path[i]['name']) - #named_path will be deprecated eventually - named_path = course_outline[outline_index]['named_path'] - self.assertEqual(len(named_path), len(path_list)) - for i in range(len(path_list)): - self.assertEqual(path_list[i], named_path[i]) - - def _setup_course_partitions(self, scheme_id='random', is_cohorted=False): - """Helper method to configure the user partitions in the course.""" - self.partition_id = 0 - self.course.user_partitions = [ - UserPartition( - self.partition_id, 'first_partition', 'First Partition', - [Group(0, 'alpha'), Group(1, 'beta')], - scheme=None, scheme_id=scheme_id - ), - ] - self.course.cohort_config = {'cohorted': is_cohorted} - self.store.update_item(self.course, self.user.id) - - def _setup_group_access(self, xblock, partition_id, group_ids): - """Helper method to configure the partition and group mapping for the given xblock.""" - xblock.group_access = {partition_id: group_ids} - self.store.update_item(xblock, self.user.id) - - def _setup_split_module(self, sub_block_category): - """Helper method to configure a split_test unit with children of type sub_block_category.""" - self._setup_course_partitions() - self.split_test = ItemFactory.create( - parent=self.unit, - category="split_test", - display_name=u"split test unit", - user_partition_id=0, - ) - sub_block_a = ItemFactory.create( - parent=self.split_test, - category=sub_block_category, - display_name=u"split test block a", - ) - sub_block_b = ItemFactory.create( - parent=self.split_test, - category=sub_block_category, - display_name=u"split test block b", - ) - self.split_test.group_id_to_child = { - str(index): url for index, url in enumerate([sub_block_a.location, sub_block_b.location]) - } - self.store.update_item(self.split_test, self.user.id) - return sub_block_a, sub_block_b - - -@ddt.ddt -class TestNonStandardCourseStructure(MobileAPITestCase, TestVideoAPIMixin, MilestonesTestCaseMixin): - """ - Tests /api/mobile/{api_version}/video_outlines/courses/{course_id} with no course set - """ - REVERSE_INFO = {'name': 'video-summary-list', 'params': ['course_id', 'api_version']} - - def setUp(self): - super(TestNonStandardCourseStructure, self).setUp() - self.chapter_under_course = ItemFactory.create( - parent=self.course, - category="chapter", - display_name=u"test factory chapter under course omega \u03a9", - ) - self.section_under_course = ItemFactory.create( - parent=self.course, - category="sequential", - display_name=u"test factory section under course omega \u03a9", - ) - self.section_under_chapter = ItemFactory.create( - parent=self.chapter_under_course, - category="sequential", - display_name=u"test factory section under chapter omega \u03a9", - ) - self.vertical_under_course = ItemFactory.create( - parent=self.course, - category="vertical", - display_name=u"test factory vertical under course omega \u03a9", - ) - self.vertical_under_section = ItemFactory.create( - parent=self.section_under_chapter, - category="vertical", - display_name=u"test factory vertical under section omega \u03a9", - ) - - @ddt.data(API_V05, API_V1) - def test_structure_course_video(self, api_version): - """ - Tests when there is a video without a vertical directly under course - """ - self.login_and_enroll() - ItemFactory.create( - parent=self.course, - category="video", - display_name=u"test factory video omega \u03a9", - ) - course_outline = self.api_response(api_version=api_version).data - self.assertEqual(len(course_outline), 1) - section_url = course_outline[0]["section_url"] - unit_url = course_outline[0]["unit_url"] - self.assertRegexpMatches(section_url, r'courseware$') - self.assertEqual(section_url, unit_url) - - self._verify_paths(course_outline, []) - - @ddt.data(API_V05, API_V1) - def test_structure_course_vert_video(self, api_version): - """ - Tests when there is a video under vertical directly under course - """ - self.login_and_enroll() - ItemFactory.create( - parent=self.vertical_under_course, - category="video", - display_name=u"test factory video omega \u03a9", - ) - course_outline = self.api_response(api_version=api_version).data - self.assertEqual(len(course_outline), 1) - section_url = course_outline[0]["section_url"] - unit_url = course_outline[0]["unit_url"] - self.assertRegexpMatches( - section_url, - r'courseware/test_factory_vertical_under_course_omega_%CE%A9/$' - ) - self.assertEqual(section_url, unit_url) - - self._verify_paths( - course_outline, - [ - u'test factory vertical under course omega \u03a9' - ] - ) - - @ddt.data(API_V05, API_V1) - def test_structure_course_chap_video(self, api_version): - """ - Tests when there is a video directly under chapter - """ - self.login_and_enroll() - - ItemFactory.create( - parent=self.chapter_under_course, - category="video", - display_name=u"test factory video omega \u03a9", - ) - course_outline = self.api_response(api_version=api_version).data - self.assertEqual(len(course_outline), 1) - section_url = course_outline[0]["section_url"] - unit_url = course_outline[0]["unit_url"] - self.assertRegexpMatches( - section_url, - r'courseware/test_factory_chapter_under_course_omega_%CE%A9/$' - ) - - self.assertEqual(section_url, unit_url) - - self._verify_paths( - course_outline, - [ - u'test factory chapter under course omega \u03a9', - ] - ) - - @ddt.data(API_V05, API_V1) - def test_structure_course_section_video(self, api_version): - """ - Tests when chapter is none, and video under section under course - """ - self.login_and_enroll() - ItemFactory.create( - parent=self.section_under_course, - category="video", - display_name=u"test factory video omega \u03a9", - ) - course_outline = self.api_response(api_version=api_version).data - self.assertEqual(len(course_outline), 1) - section_url = course_outline[0]["section_url"] - unit_url = course_outline[0]["unit_url"] - self.assertRegexpMatches( - section_url, - r'courseware/test_factory_section_under_course_omega_%CE%A9/$' - ) - - self.assertEqual(section_url, unit_url) - - self._verify_paths( - course_outline, - [ - u'test factory section under course omega \u03a9', - ] - ) - - @ddt.data(API_V05, API_V1) - def test_structure_course_chap_section_video(self, api_version): - """ - Tests when chapter and sequential exists, with a video with no vertical. - """ - self.login_and_enroll() - - ItemFactory.create( - parent=self.section_under_chapter, - category="video", - display_name=u"meow factory video omega \u03a9", - ) - course_outline = self.api_response(api_version=api_version).data - self.assertEqual(len(course_outline), 1) - section_url = course_outline[0]["section_url"] - unit_url = course_outline[0]["unit_url"] - self.assertRegexpMatches( - section_url, - ( - r'courseware/test_factory_chapter_under_course_omega_%CE%A9/' + - 'test_factory_section_under_chapter_omega_%CE%A9/$' - ) - ) - - self.assertEqual(section_url, unit_url) - - self._verify_paths( - course_outline, - [ - u'test factory chapter under course omega \u03a9', - u'test factory section under chapter omega \u03a9', - ] - ) - - @ddt.data(API_V05, API_V1) - def test_structure_course_section_vert_video(self, api_version): - """ - Tests chapter->section->vertical->unit - """ - self.login_and_enroll() - ItemFactory.create( - parent=self.vertical_under_section, - category="video", - display_name=u"test factory video omega \u03a9", - ) - course_outline = self.api_response(api_version=api_version).data - self.assertEqual(len(course_outline), 1) - section_url = course_outline[0]["section_url"] - unit_url = course_outline[0]["unit_url"] - self.assertRegexpMatches( - section_url, - ( - r'courseware/test_factory_chapter_under_course_omega_%CE%A9/' + - 'test_factory_section_under_chapter_omega_%CE%A9/$' - ) - ) - self.assertRegexpMatches( - unit_url, - ( - r'courseware/test_factory_chapter_under_course_omega_%CE%A9/' + - 'test_factory_section_under_chapter_omega_%CE%A9/1$' - ) - ) - - self._verify_paths( - course_outline, - [ - u'test factory chapter under course omega \u03a9', - u'test factory section under chapter omega \u03a9', - u'test factory vertical under section omega \u03a9' - ] - ) - - -@ddt.ddt -class TestVideoSummaryList(TestVideoAPITestCase, MobileAuthTestMixin, MobileCourseAccessTestMixin, - TestVideoAPIMixin, MilestonesTestCaseMixin): - """ - Tests for /api/mobile/{api_version}/video_outlines/courses/{course_id}.. - """ - REVERSE_INFO = {'name': 'video-summary-list', 'params': ['course_id', 'api_version']} - - @ddt.data(API_V05, API_V1) - def test_only_on_web(self, api_version): - self.login_and_enroll() - - course_outline = self.api_response(api_version=api_version).data - self.assertEqual(len(course_outline), 0) - - subid = uuid4().hex - transcripts_utils.save_subs_to_store( - { - 'start': [100], - 'end': [200], - 'text': [ - 'subs #1', - ] - }, - subid, - self.course) - - ItemFactory.create( - parent=self.unit, - category="video", - display_name=u"test video", - only_on_web=True, - subid=subid - ) - - course_outline = self.api_response(api_version=api_version).data - - self.assertEqual(len(course_outline), 1) - - self.assertIsNone(course_outline[0]["summary"]["video_url"]) - self.assertIsNone(course_outline[0]["summary"]["video_thumbnail_url"]) - self.assertEqual(course_outline[0]["summary"]["duration"], 0) - self.assertEqual(course_outline[0]["summary"]["size"], 0) - self.assertEqual(course_outline[0]["summary"]["name"], "test video") - self.assertEqual(course_outline[0]["summary"]["transcripts"], {}) - self.assertIsNone(course_outline[0]["summary"]["language"]) - self.assertEqual(course_outline[0]["summary"]["category"], "video") - self.assertTrue(course_outline[0]["summary"]["only_on_web"]) - - @ddt.data(API_V05, API_V1) - def test_mobile_api_video_profiles(self, api_version): - """ - Tests VideoSummaryList with different MobileApiConfig video_profiles - """ - self.login_and_enroll() - edx_video_id = "testing_mobile_high" - api.create_video({ - 'edx_video_id': edx_video_id, - 'status': 'test', - 'client_video_id': u"test video omega \u03a9", - 'duration': 12, - 'courses': [unicode(self.course.id)], - 'encoded_videos': [ - { - 'profile': 'youtube', - 'url': self.youtube_url, - 'file_size': 2222, - 'bitrate': 4444 - }, - { - 'profile': 'mobile_high', - 'url': self.video_url_high, - 'file_size': 111, - 'bitrate': 333 - }, - - ]}) - ItemFactory.create( - parent=self.other_unit, - category="video", - display_name=u"testing mobile high video", - edx_video_id=edx_video_id, - ) - - expected_output = { - 'all_sources': [], - 'category': u'video', - 'video_thumbnail_url': None, - 'language': u'en', - 'name': u'testing mobile high video', - 'video_url': self.video_url_high, - 'duration': 12.0, - 'transcripts': { - 'en': 'http://testserver/api/mobile/{api_version}/video_outlines/transcripts/{course_id}/testing_mobile_high_video/en'.format(api_version=api_version, course_id=self.course.id) # pylint: disable=line-too-long - }, - 'only_on_web': False, - 'encoded_videos': { - u'mobile_high': { - 'url': self.video_url_high, - 'file_size': 111 - }, - u'youtube': { - 'url': self.youtube_url, - 'file_size': 2222 - } - }, - 'size': 111 - } - - # The transcript was not entered, so it should not be found! - # This is the default behaviour at courses.edX.org, based on `FALLBACK_TO_ENGLISH_TRANSCRIPTS` - transcripts_response = self.client.get(expected_output['transcripts']['en']) - self.assertEqual(404, transcripts_response.status_code) - - with patch.dict(settings.FEATURES, FALLBACK_TO_ENGLISH_TRANSCRIPTS=False): - # Other platform installations may override this setting - # This ensures that the server don't return empty English transcripts when there's none! - self.assertFalse(self.api_response(api_version=api_version).data[0]['summary'].get('transcripts')) - - # Testing when video_profiles='mobile_low,mobile_high,youtube' - course_outline = self.api_response(api_version=api_version).data - course_outline[0]['summary'].pop("id") - self.assertEqual(course_outline[0]['summary'], expected_output) - - # Testing when there is no mobile_low, and that mobile_high doesn't show - MobileApiConfig(video_profiles="mobile_low,youtube").save() - - course_outline = self.api_response(api_version=api_version).data - - expected_output['encoded_videos'].pop('mobile_high') - expected_output['video_url'] = self.youtube_url - expected_output['size'] = 2222 - course_outline[0]['summary'].pop("id") - self.assertEqual(course_outline[0]['summary'], expected_output) - - # Testing where youtube is the default video over mobile_high - MobileApiConfig(video_profiles="youtube,mobile_high").save() - - course_outline = self.api_response(api_version=api_version).data - - expected_output['encoded_videos']['mobile_high'] = { - 'url': self.video_url_high, - 'file_size': 111 - } - - course_outline[0]['summary'].pop("id") - self.assertEqual(course_outline[0]['summary'], expected_output) - - @ddt.data(API_V05, API_V1) - def test_mobile_api_html5_sources(self, api_version): - """ - Tests VideoSummaryList without the video pipeline, using fallback HTML5 video URLs - """ - self.login_and_enroll() - descriptor = ItemFactory.create( - parent=self.other_unit, - category="video", - display_name=u"testing html5 sources", - edx_video_id=None, - source=self.video_url_high, - html5_sources=[self.video_url_low], - ) - expected_output = { - 'all_sources': [self.video_url_low, self.video_url_high], - 'category': u'video', - 'video_thumbnail_url': None, - 'language': u'en', - 'id': unicode(descriptor.scope_ids.usage_id), - 'name': u'testing html5 sources', - 'video_url': self.video_url_low, - 'duration': None, - 'transcripts': { - 'en': 'http://testserver/api/mobile/{api_version}/video_outlines/transcripts/{course_id}/testing_html5_sources/en'.format(api_version=api_version, course_id=self.course.id) # pylint: disable=line-too-long - }, - 'only_on_web': False, - 'encoded_videos': None, - 'size': 0, - } - - course_outline = self.api_response(api_version=api_version).data - self.assertEqual(course_outline[0]['summary'], expected_output) - - @ddt.data(API_V05, API_V1) - def test_video_not_in_val(self, api_version): - self.login_and_enroll() - self._create_video_with_subs() - ItemFactory.create( - parent=self.other_unit, - category="video", - edx_video_id="some_non_existent_id_in_val", - display_name=u"some non existent video in val", - html5_sources=[self.html5_video_url] - ) - - summary = self.api_response(api_version=api_version).data[1]['summary'] - self.assertEqual(summary['name'], "some non existent video in val") - self.assertIsNone(summary['encoded_videos']) - self.assertIsNone(summary['duration']) - self.assertEqual(summary['size'], 0) - self.assertEqual(summary['video_url'], self.html5_video_url) - - @ddt.data(API_V05, API_V1) - def test_course_list(self, api_version): - self.login_and_enroll() - self._create_video_with_subs() - ItemFactory.create( - parent=self.other_unit, - category="video", - display_name=u"test video omega 2 \u03a9", - html5_sources=[self.html5_video_url] - ) - ItemFactory.create( - parent=self.other_unit, - category="video", - display_name=u"test video omega 3 \u03a9", - source=self.html5_video_url - ) - ItemFactory.create( - parent=self.unit, - category="video", - edx_video_id=self.edx_video_id, - display_name=u"test draft video omega \u03a9", - visible_to_staff_only=True, - ) - - course_outline = self.api_response(api_version=api_version).data - self.assertEqual(len(course_outline), 3) - vid = course_outline[0] - self.assertIn('test_subsection_omega_%CE%A9', vid['section_url']) - self.assertIn('test_subsection_omega_%CE%A9/1', vid['unit_url']) - self.assertIn(u'test_video_omega_\u03a9', vid['summary']['id']) - self.assertEqual(vid['summary']['video_url'], self.video_url) - self.assertEqual(vid['summary']['size'], 12345) - self.assertIn('en', vid['summary']['transcripts']) - self.assertFalse(vid['summary']['only_on_web']) - self.assertEqual(course_outline[1]['summary']['video_url'], self.html5_video_url) - self.assertEqual(course_outline[1]['summary']['size'], 0) - self.assertFalse(course_outline[1]['summary']['only_on_web']) - self.assertEqual(course_outline[1]['path'][2]['name'], self.other_unit.display_name) - self.assertEqual(course_outline[1]['path'][2]['id'], unicode(self.other_unit.location)) - self.assertEqual(course_outline[2]['summary']['video_url'], self.html5_video_url) - self.assertEqual(course_outline[2]['summary']['size'], 0) - self.assertFalse(course_outline[2]['summary']['only_on_web']) - - @ddt.data(API_V05, API_V1) - def test_with_nameless_unit(self, api_version): - self.login_and_enroll() - ItemFactory.create( - parent=self.nameless_unit, - category="video", - edx_video_id=self.edx_video_id, - display_name=u"test draft video omega 2 \u03a9" - ) - course_outline = self.api_response(api_version=api_version).data - self.assertEqual(len(course_outline), 1) - self.assertEqual(course_outline[0]['path'][2]['name'], self.nameless_unit.location.block_id) - - @ddt.data(API_V05, API_V1) - def test_with_video_in_sub_section(self, api_version): - """ - Tests a non standard xml format where a video is underneath a sequential - - We are expecting to return the same unit and section url since there is - no unit vertical. - """ - self.login_and_enroll() - ItemFactory.create( - parent=self.sub_section, - category="video", - edx_video_id=self.edx_video_id, - display_name=u"video in the sub section" - ) - course_outline = self.api_response(api_version=api_version).data - self.assertEqual(len(course_outline), 1) - self.assertEqual(len(course_outline[0]['path']), 2) - section_url = course_outline[0]["section_url"] - unit_url = course_outline[0]["unit_url"] - self.assertIn( - u'courseware/test_factory_section_omega_%CE%A9/test_subsection_omega_%CE%A9', - section_url - - ) - self.assertTrue(section_url) - self.assertTrue(unit_url) - self.assertEqual(section_url, unit_url) - - @ddt.data( - *itertools.product([True, False], ["video", "problem"], [API_V05, API_V1]) - ) - @ddt.unpack - def test_with_split_block(self, is_user_staff, sub_block_category, api_version): - """Test with split_module->sub_block_category and for both staff and non-staff users.""" - self.login_and_enroll() - self.user.is_staff = is_user_staff - self.user.save() - self._setup_split_module(sub_block_category) - - video_outline = self.api_response(api_version=api_version).data - num_video_blocks = 1 if sub_block_category == "video" else 0 - self.assertEqual(len(video_outline), num_video_blocks) - for block_index in range(num_video_blocks): - self._verify_paths( - video_outline, - [ - self.section.display_name, - self.sub_section.display_name, - self.unit.display_name, - self.split_test.display_name - ], - block_index - ) - self.assertIn(u"split test block", video_outline[block_index]["summary"]["name"]) - - @ddt.data(API_V05, API_V1) - def test_with_split_vertical(self, api_version): - """Test with split_module->vertical->video structure.""" - self.login_and_enroll() - split_vertical_a, split_vertical_b = self._setup_split_module("vertical") - - ItemFactory.create( - parent=split_vertical_a, - category="video", - display_name=u"video in vertical a", - ) - ItemFactory.create( - parent=split_vertical_b, - category="video", - display_name=u"video in vertical b", - ) - - video_outline = self.api_response(api_version=api_version).data - - # user should see only one of the videos (a or b). - self.assertEqual(len(video_outline), 1) - self.assertIn(u"video in vertical", video_outline[0]["summary"]["name"]) - a_or_b = video_outline[0]["summary"]["name"][-1:] - self._verify_paths( - video_outline, - [ - self.section.display_name, - self.sub_section.display_name, - self.unit.display_name, - self.split_test.display_name, - u"split test block " + a_or_b - ], - ) - - def _create_cohorted_video(self, group_id): - """Creates a cohorted video block, giving access to only the given group_id.""" - video_block = ItemFactory.create( - parent=self.unit, - category="video", - display_name=u"video for group " + unicode(group_id), - ) - self._setup_group_access(video_block, self.partition_id, [group_id]) - - def _create_cohorted_vertical_with_video(self, group_id): - """Creates a cohorted vertical with a child video block, giving access to only the given group_id.""" - vertical_block = ItemFactory.create( - parent=self.sub_section, - category="vertical", - display_name=u"vertical for group " + unicode(group_id), - ) - self._setup_group_access(vertical_block, self.partition_id, [group_id]) - ItemFactory.create( - parent=vertical_block, - category="video", - display_name=u"video for group " + unicode(group_id), - ) - - @ddt.data( - ("_create_cohorted_video", API_V05), - ("_create_cohorted_video", API_V1), - ("_create_cohorted_vertical_with_video", API_V05), - ("_create_cohorted_vertical_with_video", API_V1), - ) - @ddt.unpack - def test_with_cohorted_content(self, content_creator_method_name, api_version): - self.login_and_enroll() - self._setup_course_partitions(scheme_id='cohort', is_cohorted=True) - - cohorts = [] - for group_id in [0, 1]: - getattr(self, content_creator_method_name)(group_id) - - cohorts.append(CohortFactory(course_id=self.course.id, name=u"Cohort " + unicode(group_id))) - link = CourseUserGroupPartitionGroup( - course_user_group=cohorts[group_id], - partition_id=self.partition_id, - group_id=group_id, - ) - link.save() - - for cohort_index in range(len(cohorts)): - # add user to this cohort - add_user_to_cohort(cohorts[cohort_index], self.user.username) - - # should only see video for this cohort - video_outline = self.api_response(api_version=api_version).data - self.assertEqual(len(video_outline), 1) - self.assertEquals( - u"video for group " + unicode(cohort_index), - video_outline[0]["summary"]["name"] - ) - - # remove user from this cohort - remove_user_from_cohort(cohorts[cohort_index], self.user.username) - - # un-cohorted user should see no videos - video_outline = self.api_response(api_version=api_version).data - self.assertEqual(len(video_outline), 0) - - # staff user sees all videos - self.user.is_staff = True - self.user.save() - video_outline = self.api_response(api_version=api_version).data - self.assertEqual(len(video_outline), 2) - - @ddt.data(API_V05, API_V1) - def test_with_hidden_blocks(self, api_version): - self.login_and_enroll() - hidden_subsection = ItemFactory.create( - parent=self.section, - category="sequential", - hide_from_toc=True, - ) - unit_within_hidden_subsection = ItemFactory.create( - parent=hidden_subsection, - category="vertical", - ) - hidden_unit = ItemFactory.create( - parent=self.sub_section, - category="vertical", - hide_from_toc=True, - ) - ItemFactory.create( - parent=unit_within_hidden_subsection, - category="video", - edx_video_id=self.edx_video_id, - ) - ItemFactory.create( - parent=hidden_unit, - category="video", - edx_video_id=self.edx_video_id, - ) - course_outline = self.api_response(api_version=api_version).data - self.assertEqual(len(course_outline), 0) - - @ddt.data(API_V05, API_V1) - def test_language(self, api_version): - self.login_and_enroll() - video = ItemFactory.create( - parent=self.nameless_unit, - category="video", - edx_video_id=self.edx_video_id, - display_name=u"test draft video omega 2 \u03a9" - ) - - language_case = namedtuple('language_case', ['transcripts', 'expected_language']) - language_cases = [ - # defaults to english - language_case({}, "en"), - # supports english - language_case({"en": 1}, "en"), - # supports another language - language_case({"lang1": 1}, "lang1"), - # returns first alphabetically-sorted language - language_case({"lang1": 1, "en": 2}, "en"), - language_case({"lang1": 1, "lang2": 2}, "lang1"), - ] - - for case in language_cases: - video.transcripts = case.transcripts - modulestore().update_item(video, self.user.id) - course_outline = self.api_response(api_version=api_version).data - self.assertEqual(len(course_outline), 1) - self.assertEqual(course_outline[0]['summary']['language'], case.expected_language) - - @ddt.data(API_V05, API_V1) - def test_transcripts(self, api_version): - self.login_and_enroll() - video = ItemFactory.create( - parent=self.nameless_unit, - category="video", - edx_video_id=self.edx_video_id, - display_name=u"test draft video omega 2 \u03a9" - ) - - transcript_case = namedtuple('transcript_case', ['transcripts', 'english_subtitle', 'expected_transcripts']) - transcript_cases = [ - # defaults to english - transcript_case({}, "", ["en"]), - transcript_case({}, "en-sub", ["en"]), - # supports english - transcript_case({"en": 1}, "", ["en"]), - transcript_case({"en": 1}, "en-sub", ["en"]), - # keeps both english and other languages - transcript_case({"lang1": 1, "en": 2}, "", ["lang1", "en"]), - transcript_case({"lang1": 1, "en": 2}, "en-sub", ["lang1", "en"]), - # adds english to list of languages only if english_subtitle is specified - transcript_case({"lang1": 1, "lang2": 2}, "", ["lang1", "lang2"]), - transcript_case({"lang1": 1, "lang2": 2}, "en-sub", ["lang1", "lang2", "en"]), - ] - - for case in transcript_cases: - video.transcripts = case.transcripts - video.sub = case.english_subtitle - modulestore().update_item(video, self.user.id) - course_outline = self.api_response(api_version=api_version).data - self.assertEqual(len(course_outline), 1) - self.assertSetEqual( - set(course_outline[0]['summary']['transcripts'].keys()), - set(case.expected_transcripts) - ) - - @ddt.data( - ({}, '', [], ['en'], API_V05), - ({}, '', [], ['en'], API_V1), - ({}, '', ['de'], ['de'], API_V05), - ({}, '', ['de'], ['de'], API_V1), - ({}, '', ['en', 'de'], ['en', 'de'], API_V05), - ({}, '', ['en', 'de'], ['en', 'de'], API_V1), - ({}, 'en-subs', ['de'], ['en', 'de'], API_V05), - ({}, 'en-subs', ['de'], ['en', 'de'], API_V1), - ({'uk': 1}, 'en-subs', ['de'], ['en', 'uk', 'de'], API_V05), - ({'uk': 1}, 'en-subs', ['de'], ['en', 'uk', 'de'], API_V1), - ({'uk': 1, 'de': 1}, 'en-subs', ['de', 'en'], ['en', 'uk', 'de'], API_V05), - ({'uk': 1, 'de': 1}, 'en-subs', ['de', 'en'], ['en', 'uk', 'de'], API_V1), - ) - @ddt.unpack - @patch('xmodule.video_module.transcripts_utils.edxval_api.get_available_transcript_languages') - def test_val_transcripts_with_feature_enabled(self, transcripts, english_sub, val_transcripts, - expected_transcripts, api_version, - mock_get_transcript_languages): - self.login_and_enroll() - video = ItemFactory.create( - parent=self.nameless_unit, - category="video", - edx_video_id=self.edx_video_id, - display_name=u"test draft video omega 2 \u03a9" - ) - - mock_get_transcript_languages.return_value = val_transcripts - video.transcripts = transcripts - video.sub = english_sub - modulestore().update_item(video, self.user.id) - - course_outline = self.api_response(api_version=api_version).data - self.assertEqual(len(course_outline), 1) - self.assertItemsEqual(course_outline[0]['summary']['transcripts'].keys(), expected_transcripts) - - -@ddt.ddt -class TestTranscriptsDetail(TestVideoAPITestCase, MobileAuthTestMixin, MobileCourseAccessTestMixin, - TestVideoAPIMixin, MilestonesTestCaseMixin): - """ - Tests for /api/mobile/{api_version}/video_outlines/transcripts/{course_id}.. - """ - REVERSE_INFO = {'name': 'video-transcripts-detail', 'params': ['course_id', 'api_version']} - - def setUp(self): - super(TestTranscriptsDetail, self).setUp() - self.video = self._create_video_with_subs() - - def reverse_url(self, reverse_args=None, **kwargs): - reverse_args = reverse_args or {} - reverse_args.update({ - 'block_id': self.video.location.block_id, - 'lang': kwargs.get('lang', 'en'), - }) - return super(TestTranscriptsDetail, self).reverse_url(reverse_args, **kwargs) - - @ddt.data(API_V05, API_V1) - def test_incorrect_language(self, api_version): - self.login_and_enroll() - self.api_response(expected_response_code=404, lang='pl', api_version=api_version) - - @ddt.data(API_V05, API_V1) - def test_transcript_with_unicode_file_name(self, api_version): - self.video = self._create_video_with_subs(custom_subid=u'你好') - self.login_and_enroll() - self.api_response(expected_response_code=200, lang='en', api_version=api_version) - - @ddt.data(API_V05, API_V1) - @patch( - 'xmodule.video_module.transcripts_utils.edxval_api.get_available_transcript_languages', - Mock(return_value=['uk']), - ) - @patch('xmodule.video_module.transcripts_utils.edxval_api.get_video_transcript_data') - def test_val_transcript(self, api_version, mock_get_video_transcript_content): - """ - Tests transcript retrieval view with val transcripts. - """ - mock_get_video_transcript_content.return_value = { - 'content': json.dumps({ - 'start': [10], - 'end': [100], - 'text': [u'Hi, welcome to Edx.'], - }), - 'file_name': 'edx.sjson' - } - - self.login_and_enroll() - # Now, make request to retrieval endpoint - response = self.api_response(expected_response_code=200, lang='uk', api_version=api_version) - - # Expected headers - expected_content = u'0\n00:00:00,010 --> 00:00:00,100\nHi, welcome to Edx.\n\n' - expected_headers = { - 'Content-Disposition': 'attachment; filename="edx.srt"', - 'Content-Type': 'application/x-subrip; charset=utf-8' - } - # Assert the actual response - self.assertEqual(response.content, expected_content) - for attribute, value in expected_headers.iteritems(): - self.assertEqual(response.get(attribute), value) diff --git a/lms/djangoapps/mobile_api/video_outlines/urls.py b/lms/djangoapps/mobile_api/video_outlines/urls.py deleted file mode 100644 index 116a2b18ee..0000000000 --- a/lms/djangoapps/mobile_api/video_outlines/urls.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -URLs for video outline API -""" - -from django.conf import settings -from django.conf.urls import url - -from .views import VideoSummaryList, VideoTranscripts - -urlpatterns = [ - url( - r'^courses/{}$'.format(settings.COURSE_ID_PATTERN), - VideoSummaryList.as_view(), - name='video-summary-list' - ), - url( - r'^transcripts/{}/(?P[^/]*)/(?P[^/]*)$'.format(settings.COURSE_ID_PATTERN), - VideoTranscripts.as_view(), - name='video-transcripts-detail' - ), -] diff --git a/lms/djangoapps/mobile_api/video_outlines/views.py b/lms/djangoapps/mobile_api/video_outlines/views.py deleted file mode 100644 index 168178cd6d..0000000000 --- a/lms/djangoapps/mobile_api/video_outlines/views.py +++ /dev/null @@ -1,127 +0,0 @@ -""" -Video Outlines - -We only provide the listing view for a video outline, and video outlines are -only displayed at the course level. This is because it makes it a lot easier to -optimize and reason about, and it avoids having to tackle the bigger problem of -general XBlock representation in this rather specialized formatting. -""" -from functools import partial - -from django.http import Http404, HttpResponse -from opaque_keys.edx.locator import BlockUsageLocator -from rest_framework import generics -from rest_framework.response import Response - -from mobile_api.models import MobileApiConfig -from xmodule.exceptions import NotFoundError -from xmodule.modulestore.django import modulestore -from xmodule.video_module.transcripts_utils import get_transcript - -from ..decorators import mobile_course_access, mobile_view -from .serializers import BlockOutline, video_summary - - -@mobile_view() -class VideoSummaryList(generics.ListAPIView): - """ - **Use Case** - - Get a list of all videos in the specified course. You can use the - video_url value to access the video file. - - **Example Request** - - GET /api/mobile/v0.5/video_outlines/courses/{organization}/{course_number}/{course_run} - - **Response Values** - - If the request is successful, the request returns an HTTP 200 "OK" - response along with an array of videos in the course. The array - includes the following information for each video. - - * named_path: An array that consists of the display names of the - courseware objects in the path to the video. - * path: An array that specifies the complete path to the video in - the courseware hierarchy. The array contains the following - values. - - * category: The type of division in the course outline. - Possible values are "chapter", "sequential", and "vertical". - * name: The display name for the object. - * id: The The unique identifier for the video. - - * section_url: The URL to the first page of the section that - contains the video in the Learning Management System. - * summary: An array of data about the video that includes the - following values. - - * category: The type of component. This value will always be "video". - * duration: The length of the video, if available. - * id: The unique identifier for the video. - * language: The language code for the video. - * name: The display name of the video. - * size: The size of the video file. - * transcripts: An array of language codes and URLs to available - video transcripts. Use the URL value to access a transcript - for the video. - * video_thumbnail_url: The URL to the thumbnail image for the - video, if available. - * video_url: The URL to the video file. Use this value to access - the video. - - * unit_url: The URL to the unit that contains the video in the Learning - Management System. - """ - - @mobile_course_access(depth=None) - def list(self, request, course, *args, **kwargs): - video_profiles = MobileApiConfig.get_video_profiles() - video_outline = list( - BlockOutline( - course.id, - course, - {"video": partial(video_summary, video_profiles)}, - request, - video_profiles, - kwargs.get('api_version') - ) - ) - return Response(video_outline) - - -@mobile_view() -class VideoTranscripts(generics.RetrieveAPIView): - """ - **Use Case** - - Get a transcript for a specified video and language. - - **Example request** - - GET /api/mobile/v0.5/video_outlines/transcripts/{organization}/{course_number}/{course_run}/{video ID}/{language code} - - **Response Values** - - If the request is successful, the request returns an HTTP 200 "OK" - response along with an .srt file that you can download. - - """ - - @mobile_course_access() - def get(self, request, course, *args, **kwargs): - block_id = kwargs['block_id'] - lang = kwargs['lang'] - - usage_key = BlockUsageLocator(course.id, block_type='video', block_id=block_id) - video_descriptor = modulestore().get_item(usage_key) - - try: - content, filename, mimetype = get_transcript(video_descriptor, lang=lang) - except NotFoundError: - raise Http404(u'Transcript not found for {}, lang: {}'.format(block_id, lang)) - - response = HttpResponse(content, content_type=mimetype) - response['Content-Disposition'] = u'attachment; filename="{}"'.format(filename) - - return response