Commit 037ef3be by Nimisha Asthagiri

Video module support for student_view_json.

parent 65e330e8
...@@ -250,9 +250,10 @@ class VideoStudentViewHandlers(object): ...@@ -250,9 +250,10 @@ class VideoStudentViewHandlers(object):
response.content_type = Transcript.mime_types['sjson'] response.content_type = Transcript.mime_types['sjson']
elif dispatch == 'download': elif dispatch == 'download':
lang = request.GET.get('lang', None)
try: try:
transcript_content, transcript_filename, transcript_mime_type = self.get_transcript( 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): except (NotFoundError, ValueError, KeyError, UnicodeDecodeError):
log.debug("Video@download exception") log.debug("Video@download exception")
......
...@@ -20,12 +20,12 @@ import logging ...@@ -20,12 +20,12 @@ import logging
import random import random
from collections import OrderedDict from collections import OrderedDict
from operator import itemgetter from operator import itemgetter
from lxml import etree from lxml import etree
from pkg_resources import resource_string from pkg_resources import resource_string
from django.conf import settings from django.conf import settings
from openedx.core.lib.cache_utils import memoize_in_request_cache
from xblock.core import XBlock from xblock.core import XBlock
from xblock.fields import ScopeIds from xblock.fields import ScopeIds
from xblock.runtime import KvsFieldData from xblock.runtime import KvsFieldData
...@@ -329,6 +329,7 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers, ...@@ -329,6 +329,7 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
return self.system.render_template('video.html', context) return self.system.render_template('video.html', context)
@XBlock.wants("request_cache")
@XBlock.wants("settings") @XBlock.wants("settings")
class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandlers, class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandlers,
TabsEditingDescriptor, EmptyDataRawDescriptor): TabsEditingDescriptor, EmptyDataRawDescriptor):
...@@ -722,7 +723,7 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler ...@@ -722,7 +723,7 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
if self.sub: if self.sub:
_update_transcript_for_index() _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: if self.transcripts:
for language in self.transcripts.keys(): for language in self.transcripts.keys():
_update_transcript_for_index(language) _update_transcript_for_index(language)
...@@ -734,3 +735,78 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler ...@@ -734,3 +735,78 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
xblock_body["content_type"] = "Video" xblock_body["content_type"] = "Video"
return xblock_body 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,
}
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Video xmodule tests in mongo.""" """Video xmodule tests in mongo."""
import ddt
import itertools
import json import json
from collections import OrderedDict from collections import OrderedDict
...@@ -13,7 +15,7 @@ from django.test.utils import override_settings ...@@ -13,7 +15,7 @@ from django.test.utils import override_settings
from xmodule.video_module import VideoDescriptor, bumper_utils, video_utils from xmodule.video_module import VideoDescriptor, bumper_utils, video_utils
from xmodule.x_module import STUDENT_VIEW 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 xmodule.tests.test_import import DummySystem
from edxval.api import ( from edxval.api import (
...@@ -861,6 +863,119 @@ class TestVideoDescriptorInitialization(BaseTestXmodule): ...@@ -861,6 +863,119 @@ class TestVideoDescriptorInitialization(BaseTestXmodule):
self.assertFalse(self.item_descriptor.download_video) 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') @attr('shard_1')
class VideoDescriptorTest(TestCase, VideoDescriptorTestBase): class VideoDescriptorTest(TestCase, VideoDescriptorTestBase):
""" """
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment