Video module support for student_view_json.
This commit is contained in:
@@ -250,9 +250,10 @@ class VideoStudentViewHandlers(object):
|
||||
response.content_type = Transcript.mime_types['sjson']
|
||||
|
||||
elif dispatch == 'download':
|
||||
lang = request.GET.get('lang', None)
|
||||
try:
|
||||
transcript_content, transcript_filename, transcript_mime_type = self.get_transcript(
|
||||
transcripts, transcript_format=self.transcript_download_format
|
||||
transcripts, transcript_format=self.transcript_download_format, lang=lang
|
||||
)
|
||||
except (NotFoundError, ValueError, KeyError, UnicodeDecodeError):
|
||||
log.debug("Video@download exception")
|
||||
|
||||
@@ -20,12 +20,12 @@ import logging
|
||||
import random
|
||||
from collections import OrderedDict
|
||||
from operator import itemgetter
|
||||
|
||||
from lxml import etree
|
||||
from pkg_resources import resource_string
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from openedx.core.lib.cache_utils import memoize_in_request_cache
|
||||
from xblock.core import XBlock
|
||||
from xblock.fields import ScopeIds
|
||||
from xblock.runtime import KvsFieldData
|
||||
@@ -329,6 +329,7 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
|
||||
return self.system.render_template('video.html', context)
|
||||
|
||||
|
||||
@XBlock.wants("request_cache")
|
||||
@XBlock.wants("settings")
|
||||
class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandlers,
|
||||
TabsEditingDescriptor, EmptyDataRawDescriptor):
|
||||
@@ -722,7 +723,7 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
|
||||
if self.sub:
|
||||
_update_transcript_for_index()
|
||||
|
||||
# check to see if there are transcripts in other languages besides default transcript
|
||||
# Check to see if there are transcripts in other languages besides default transcript
|
||||
if self.transcripts:
|
||||
for language in self.transcripts.keys():
|
||||
_update_transcript_for_index(language)
|
||||
@@ -734,3 +735,78 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
|
||||
xblock_body["content_type"] = "Video"
|
||||
|
||||
return xblock_body
|
||||
|
||||
@property
|
||||
def request_cache(self):
|
||||
"""
|
||||
Returns the request_cache from the runtime.
|
||||
"""
|
||||
return self.runtime.service(self, "request_cache")
|
||||
|
||||
@memoize_in_request_cache('request_cache')
|
||||
def get_cached_val_data_for_course(self, video_profile_names, course_id):
|
||||
"""
|
||||
Returns the VAL data for the requested video profiles for the given course.
|
||||
"""
|
||||
return edxval_api.get_video_info_for_course_and_profiles(unicode(course_id), video_profile_names)
|
||||
|
||||
def student_view_json(self, context):
|
||||
"""
|
||||
Returns a JSON representation of the student_view of this XModule.
|
||||
The contract of the JSON content is between the caller and the particular XModule.
|
||||
"""
|
||||
# Honor only_on_web
|
||||
if self.only_on_web:
|
||||
return {"only_on_web": True}
|
||||
|
||||
encoded_videos = {}
|
||||
val_video_data = {}
|
||||
|
||||
# Check in VAL data first if edx_video_id exists
|
||||
if self.edx_video_id:
|
||||
video_profile_names = context.get("profiles", [])
|
||||
|
||||
# get and cache bulk VAL data for course
|
||||
val_course_data = self.get_cached_val_data_for_course(video_profile_names, self.location.course_key)
|
||||
val_video_data = val_course_data.get(self.edx_video_id, {})
|
||||
|
||||
# Get the encoded videos if data from VAL is found
|
||||
if val_video_data:
|
||||
encoded_videos = val_video_data.get('profiles', {})
|
||||
|
||||
# If information for this edx_video_id is not found in the bulk course data, make a
|
||||
# separate request for this individual edx_video_id, unless cache misses are disabled.
|
||||
# This is useful/required for videos that don't have a course designated, such as the introductory video
|
||||
# that is shared across many courses. However, this results in a separate database request so watch
|
||||
# out for any performance hit if many such videos exist in a course. Set the 'allow_cache_miss' parameter
|
||||
# to False to disable this fall back.
|
||||
elif context.get("allow_cache_miss", "True").lower() == "true":
|
||||
try:
|
||||
val_video_data = edxval_api.get_video_info(self.edx_video_id)
|
||||
# Unfortunately, the VAL API is inconsistent in how it returns the encodings, so remap here.
|
||||
for enc_vid in val_video_data.pop('encoded_videos'):
|
||||
encoded_videos[enc_vid['profile']] = {key: enc_vid[key] for key in ["url", "file_size"]}
|
||||
except edxval_api.ValVideoNotFoundError:
|
||||
pass
|
||||
|
||||
# Fall back to other video URLs in the video module if not found in VAL
|
||||
if not encoded_videos:
|
||||
video_url = self.html5_sources[0] if self.html5_sources else self.source
|
||||
if video_url:
|
||||
encoded_videos["fallback"] = {
|
||||
"url": video_url,
|
||||
"file_size": 0, # File size is unknown for fallback URLs
|
||||
}
|
||||
|
||||
transcripts_info = self.get_transcripts_info()
|
||||
transcripts = {
|
||||
lang: self.runtime.handler_url(self, 'transcript', 'download', query="lang=" + lang, thirdparty=True)
|
||||
for lang in self.available_translations(transcripts_info, verify_assets=False)
|
||||
}
|
||||
|
||||
return {
|
||||
"only_on_web": self.only_on_web,
|
||||
"duration": val_video_data.get('duration', None),
|
||||
"transcripts": transcripts,
|
||||
"encoded_videos": encoded_videos,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Video xmodule tests in mongo."""
|
||||
import ddt
|
||||
import itertools
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
|
||||
@@ -13,7 +15,7 @@ from django.test.utils import override_settings
|
||||
|
||||
from xmodule.video_module import VideoDescriptor, bumper_utils, video_utils
|
||||
from xmodule.x_module import STUDENT_VIEW
|
||||
from xmodule.tests.test_video import VideoDescriptorTestBase
|
||||
from xmodule.tests.test_video import VideoDescriptorTestBase, instantiate_descriptor
|
||||
from xmodule.tests.test_import import DummySystem
|
||||
|
||||
from edxval.api import (
|
||||
@@ -861,6 +863,119 @@ class TestVideoDescriptorInitialization(BaseTestXmodule):
|
||||
self.assertFalse(self.item_descriptor.download_video)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestVideoDescriptorStudentViewJson(TestCase):
|
||||
"""
|
||||
Tests for the student_view_json method on VideoDescriptor.
|
||||
"""
|
||||
TEST_DURATION = 111.0
|
||||
TEST_PROFILE = "mobile"
|
||||
TEST_SOURCE_URL = "http://www.example.com/source.mp4"
|
||||
TEST_LANGUAGE = "ge"
|
||||
TEST_ENCODED_VIDEO = {
|
||||
'profile': TEST_PROFILE,
|
||||
'bitrate': 333,
|
||||
'url': 'http://example.com/video',
|
||||
'file_size': 222,
|
||||
}
|
||||
TEST_EDX_VIDEO_ID = 'test_edx_video_id'
|
||||
|
||||
def setUp(self):
|
||||
super(TestVideoDescriptorStudentViewJson, self).setUp()
|
||||
sample_xml = (
|
||||
"<video display_name='Test Video'> " +
|
||||
"<source src='" + self.TEST_SOURCE_URL + "'/> " +
|
||||
"<transcript language='" + self.TEST_LANGUAGE + "' src='german_translation.srt' /> " +
|
||||
"</video>"
|
||||
)
|
||||
self.transcript_url = "transcript_url"
|
||||
self.video = instantiate_descriptor(data=sample_xml)
|
||||
self.video.runtime.handler_url = Mock(return_value=self.transcript_url)
|
||||
|
||||
def setup_val_video(self, associate_course_in_val=False):
|
||||
"""
|
||||
Creates a video entry in VAL.
|
||||
Arguments:
|
||||
associate_course - If True, associates the test course with the video in VAL.
|
||||
"""
|
||||
create_profile('mobile')
|
||||
create_video({
|
||||
'edx_video_id': self.TEST_EDX_VIDEO_ID,
|
||||
'client_video_id': 'test_client_video_id',
|
||||
'duration': self.TEST_DURATION,
|
||||
'status': 'dummy',
|
||||
'encoded_videos': [self.TEST_ENCODED_VIDEO],
|
||||
'courses': [self.video.location.course_key] if associate_course_in_val else [],
|
||||
})
|
||||
self.val_video = get_video_info(self.TEST_EDX_VIDEO_ID) # pylint: disable=attribute-defined-outside-init
|
||||
|
||||
def get_result(self, allow_cache_miss=True):
|
||||
"""
|
||||
Returns the result from calling the video's student_view_json method.
|
||||
Arguments:
|
||||
allow_cache_miss is passed in the context to the student_view_json method.
|
||||
"""
|
||||
context = {
|
||||
"profiles": [self.TEST_PROFILE],
|
||||
"allow_cache_miss": "True" if allow_cache_miss else "False"
|
||||
}
|
||||
return self.video.student_view_json(context)
|
||||
|
||||
def verify_result_with_fallback_url(self, result):
|
||||
"""
|
||||
Verifies the result is as expected when returning "fallback" video data (not from VAL).
|
||||
"""
|
||||
self.assertDictEqual(
|
||||
result,
|
||||
{
|
||||
"only_on_web": False,
|
||||
"duration": None,
|
||||
"transcripts": {self.TEST_LANGUAGE: self.transcript_url},
|
||||
"encoded_videos": {"fallback": {"url": self.TEST_SOURCE_URL, "file_size": 0}},
|
||||
}
|
||||
)
|
||||
|
||||
def verify_result_with_val_profile(self, result):
|
||||
"""
|
||||
Verifies the result is as expected when returning video data from VAL.
|
||||
"""
|
||||
self.assertDictContainsSubset(
|
||||
result.pop("encoded_videos")[self.TEST_PROFILE],
|
||||
self.TEST_ENCODED_VIDEO,
|
||||
)
|
||||
self.assertDictEqual(
|
||||
result,
|
||||
{
|
||||
"only_on_web": False,
|
||||
"duration": self.TEST_DURATION,
|
||||
"transcripts": {self.TEST_LANGUAGE: self.transcript_url},
|
||||
}
|
||||
)
|
||||
|
||||
def test_only_on_web(self):
|
||||
self.video.only_on_web = True
|
||||
result = self.get_result()
|
||||
self.assertDictEqual(result, {"only_on_web": True})
|
||||
|
||||
def test_no_edx_video_id(self):
|
||||
result = self.get_result()
|
||||
self.verify_result_with_fallback_url(result)
|
||||
|
||||
@ddt.data(
|
||||
*itertools.product([True, False], [True, False], [True, False])
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_with_edx_video_id(self, allow_cache_miss, video_exists_in_val, associate_course_in_val):
|
||||
self.video.edx_video_id = self.TEST_EDX_VIDEO_ID
|
||||
if video_exists_in_val:
|
||||
self.setup_val_video(associate_course_in_val)
|
||||
result = self.get_result(allow_cache_miss)
|
||||
if video_exists_in_val and (associate_course_in_val or allow_cache_miss):
|
||||
self.verify_result_with_val_profile(result)
|
||||
else:
|
||||
self.verify_result_with_fallback_url(result)
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class VideoDescriptorTest(TestCase, VideoDescriptorTestBase):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user