Merge pull request #20384 from open-craft/symbolist/video-xblock
VideoModule to VideoBlock [SE-602]
This commit is contained in:
@@ -18,6 +18,7 @@ from openedx.core.djangoapps.video_config.models import (
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
from xmodule.video_module import VideoBlock
|
||||
from xmodule.video_module.transcripts_utils import save_to_store
|
||||
from edxval import api as api
|
||||
from testfixtures import LogCapture
|
||||
@@ -105,9 +106,9 @@ class TestMigrateTranscripts(ModuleStoreTestCase):
|
||||
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
|
||||
show_captions="false"
|
||||
download_track="false"
|
||||
start_time="00:00:01"
|
||||
start_time="1.0"
|
||||
download_video="false"
|
||||
end_time="00:01:00">
|
||||
end_time="60.0">
|
||||
<source src="http://www.example.com/source.mp4"/>
|
||||
<track src="http://www.example.com/track"/>
|
||||
<handout src="http://www.example.com/handout"/>
|
||||
@@ -122,9 +123,9 @@ class TestMigrateTranscripts(ModuleStoreTestCase):
|
||||
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
|
||||
show_captions="false"
|
||||
download_track="false"
|
||||
start_time="00:00:01"
|
||||
start_time="1.0"
|
||||
download_video="false"
|
||||
end_time="00:01:00">
|
||||
end_time="60.0">
|
||||
<source src="http://www.example.com/source.mp4"/>
|
||||
<track src="http://www.example.com/track"/>
|
||||
<handout src="http://www.example.com/handout"/>
|
||||
@@ -133,11 +134,11 @@ class TestMigrateTranscripts(ModuleStoreTestCase):
|
||||
'''
|
||||
self.video_descriptor = ItemFactory.create(
|
||||
parent_location=self.course.location, category='video',
|
||||
data={'data': video_sample_xml}
|
||||
**VideoBlock.parse_video_xml(video_sample_xml)
|
||||
)
|
||||
self.video_descriptor_2 = ItemFactory.create(
|
||||
parent_location=self.course_2.location, category='video',
|
||||
data={'data': video_sample_xml_2}
|
||||
**VideoBlock.parse_video_xml(video_sample_xml_2)
|
||||
)
|
||||
|
||||
save_to_store(SRT_FILEDATA, 'subs_grmtran1.srt', 'text/srt', self.video_descriptor.location)
|
||||
|
||||
@@ -54,6 +54,7 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, chec
|
||||
from xmodule.modulestore.xml_exporter import export_course_to_xml
|
||||
from xmodule.modulestore.xml_importer import import_course_from_xml, perform_xlint
|
||||
from xmodule.seq_module import SequenceDescriptor
|
||||
from xmodule.video_module import VideoBlock
|
||||
|
||||
TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE)
|
||||
TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'] = 'test_xcontent_%s' % uuid4().hex
|
||||
@@ -1815,15 +1816,15 @@ class MetadataSaveTestCase(ContentStoreTestCase):
|
||||
<video display_name="Test Video"
|
||||
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
|
||||
show_captions="false"
|
||||
from="00:00:01"
|
||||
to="00:01:00">
|
||||
from="1.0"
|
||||
to="60.0">
|
||||
<source src="http://www.example.com/file.mp4"/>
|
||||
<track src="http://www.example.com/track"/>
|
||||
</video>
|
||||
"""
|
||||
self.video_descriptor = ItemFactory.create(
|
||||
parent_location=course.location, category='video',
|
||||
data={'data': video_sample_xml}
|
||||
**VideoBlock.parse_video_xml(video_sample_xml)
|
||||
)
|
||||
|
||||
def test_metadata_not_persistence(self):
|
||||
@@ -1840,7 +1841,6 @@ class MetadataSaveTestCase(ContentStoreTestCase):
|
||||
'youtube_id_1_5',
|
||||
'start_time',
|
||||
'end_time',
|
||||
'source',
|
||||
'html5_sources',
|
||||
'track'
|
||||
}
|
||||
|
||||
@@ -718,7 +718,7 @@ class TestCourseReIndex(CourseTestCase):
|
||||
course_id=unicode(self.course.id))
|
||||
self.assertEqual(response['total'], 1)
|
||||
|
||||
@mock.patch('xmodule.video_module.VideoDescriptor.index_dictionary')
|
||||
@mock.patch('xmodule.video_module.VideoBlock.index_dictionary')
|
||||
def test_reindex_video_error_json_responses(self, mock_index_dictionary):
|
||||
"""
|
||||
Test json response with mocked error data for video
|
||||
@@ -828,7 +828,7 @@ class TestCourseReIndex(CourseTestCase):
|
||||
course_id=unicode(self.course.id))
|
||||
self.assertEqual(response['total'], 1)
|
||||
|
||||
@mock.patch('xmodule.video_module.VideoDescriptor.index_dictionary')
|
||||
@mock.patch('xmodule.video_module.VideoBlock.index_dictionary')
|
||||
def test_indexing_video_error_responses(self, mock_index_dictionary):
|
||||
"""
|
||||
Test do_course_reindex response with mocked error data for video
|
||||
|
||||
@@ -21,6 +21,7 @@ from xmodule.contentstore.content import StaticContent
|
||||
from xmodule.contentstore.django import contentstore
|
||||
from xmodule.exceptions import NotFoundError
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.video_module import VideoBlock
|
||||
from xmodule.video_module.transcripts_utils import (
|
||||
GetTranscriptsFromYouTubeException,
|
||||
Transcript,
|
||||
@@ -94,7 +95,9 @@ class BaseTranscripts(CourseTestCase):
|
||||
self.item = modulestore().get_item(self.video_usage_key)
|
||||
# hI10vDNYz4M - valid Youtube ID with transcripts.
|
||||
# JMD_ifUUfsU, AKqURZnYqpk, DYpADpL7jAY - valid Youtube IDs without transcripts.
|
||||
self.item.data = '<video youtube="0.75:JMD_ifUUfsU,1.0:hI10vDNYz4M,1.25:AKqURZnYqpk,1.50:DYpADpL7jAY" />'
|
||||
self.set_fields_from_xml(
|
||||
self.item, '<video youtube="0.75:JMD_ifUUfsU,1.0:hI10vDNYz4M,1.25:AKqURZnYqpk,1.50:DYpADpL7jAY" />'
|
||||
)
|
||||
modulestore().update_item(self.item, self.user.id)
|
||||
|
||||
self.item = modulestore().get_item(self.video_usage_key)
|
||||
@@ -129,7 +132,7 @@ class BaseTranscripts(CourseTestCase):
|
||||
response = self.client.ajax_post('/xblock/', data)
|
||||
usage_key = self._get_usage_key(response)
|
||||
item = modulestore().get_item(usage_key)
|
||||
item.data = '<non_video youtube="0.75:JMD_ifUUfsU,1.0:hI10vDNYz4M" />'
|
||||
self.set_fields_from_xml(self.item, '<non_video youtube="0.75:JMD_ifUUfsU,1.0:hI10vDNYz4M" />')
|
||||
modulestore().update_item(item, self.user.id)
|
||||
|
||||
return usage_key
|
||||
@@ -139,6 +142,11 @@ class BaseTranscripts(CourseTestCase):
|
||||
self.assertEqual(response.status_code, expected_status_code)
|
||||
self.assertEqual(response_content['status'], expected_message)
|
||||
|
||||
def set_fields_from_xml(self, item, xml):
|
||||
fields_data = VideoBlock.parse_video_xml(xml)
|
||||
for key, value in fields_data.items():
|
||||
setattr(item, key, value)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestUploadTranscripts(BaseTranscripts):
|
||||
@@ -836,7 +844,7 @@ class TestCheckTranscripts(BaseTranscripts):
|
||||
"""
|
||||
def test_success_download_nonyoutube(self):
|
||||
subs_id = str(uuid4())
|
||||
self.item.data = textwrap.dedent(u"""
|
||||
self.set_fields_from_xml(self.item, u"""
|
||||
<video youtube="" sub="{}">
|
||||
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4"/>
|
||||
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.webm"/>
|
||||
@@ -885,7 +893,7 @@ class TestCheckTranscripts(BaseTranscripts):
|
||||
remove_subs_from_store(subs_id, self.item)
|
||||
|
||||
def test_check_youtube(self):
|
||||
self.item.data = '<video youtube="1:JMD_ifUUfsU" />'
|
||||
self.set_fields_from_xml(self.item, '<video youtube="1:JMD_ifUUfsU" />')
|
||||
modulestore().update_item(self.item, self.user.id)
|
||||
|
||||
subs = {
|
||||
@@ -931,7 +939,7 @@ class TestCheckTranscripts(BaseTranscripts):
|
||||
"""
|
||||
Test that the transcripts are fetched correctly when the the transcript name is set
|
||||
"""
|
||||
self.item.data = '<video youtube="good_id_2" />'
|
||||
self.set_fields_from_xml(self.item, '<video youtube="good_id_2" />')
|
||||
modulestore().update_item(self.item, self.user.id)
|
||||
|
||||
subs = {
|
||||
@@ -1030,13 +1038,13 @@ class TestCheckTranscripts(BaseTranscripts):
|
||||
usage_key = self._get_usage_key(resp)
|
||||
subs_id = str(uuid4())
|
||||
item = modulestore().get_item(usage_key)
|
||||
item.data = textwrap.dedent(u"""
|
||||
self.set_fields_from_xml(self.item, (u"""
|
||||
<not_video youtube="" sub="{}">
|
||||
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4"/>
|
||||
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.webm"/>
|
||||
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.ogv"/>
|
||||
</videoalpha>
|
||||
""".format(subs_id))
|
||||
</not_video>
|
||||
""".format(subs_id)))
|
||||
modulestore().update_item(item, self.user.id)
|
||||
|
||||
subs = {
|
||||
@@ -1078,13 +1086,13 @@ class TestCheckTranscripts(BaseTranscripts):
|
||||
}
|
||||
|
||||
# video_transcript_feature.return_value = feature_enabled
|
||||
self.item.data = textwrap.dedent("""
|
||||
self.set_fields_from_xml(self.item, (u"""
|
||||
<video youtube="" sub="" edx_video_id="123">
|
||||
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4"/>
|
||||
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.webm"/>
|
||||
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.ogv"/>
|
||||
</video>
|
||||
""")
|
||||
"""))
|
||||
modulestore().update_item(self.item, self.user.id)
|
||||
|
||||
# Make request to check transcript view
|
||||
|
||||
@@ -52,7 +52,7 @@ update_module_store_settings(
|
||||
)
|
||||
|
||||
# Needed to enable licensing on video modules
|
||||
XBLOCK_SETTINGS.update({'VideoDescriptor': {'licensing_enabled': True}})
|
||||
XBLOCK_SETTINGS.update({'VideoBlock': {'licensing_enabled': True}})
|
||||
|
||||
# Capture the console log via template includes, until webdriver supports log capture again
|
||||
CAPTURE_CONSOLE_LOG = True
|
||||
|
||||
@@ -1424,10 +1424,8 @@ ELASTIC_FIELD_MAPPINGS = {
|
||||
}
|
||||
|
||||
XBLOCK_SETTINGS = {
|
||||
"VideoDescriptor": {
|
||||
"licensing_enabled": FEATURES.get("LICENSING", False)
|
||||
},
|
||||
'VideoModule': {
|
||||
"VideoBlock": {
|
||||
"licensing_enabled": FEATURES.get("LICENSING", False),
|
||||
'YOUTUBE_API_KEY': YOUTUBE_API_KEY
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ FEATURES['ENTRANCE_EXAMS'] = True
|
||||
################################ COURSE LICENSES ################################
|
||||
FEATURES['LICENSING'] = True
|
||||
# Needed to enable licensing on video modules
|
||||
XBLOCK_SETTINGS.update({'VideoDescriptor': {'licensing_enabled': True}})
|
||||
XBLOCK_SETTINGS.update({'VideoBlock': {'licensing_enabled': True}})
|
||||
|
||||
################################ SEARCH INDEX ################################
|
||||
FEATURES['ENABLE_COURSEWARE_INDEX'] = True
|
||||
|
||||
@@ -501,8 +501,8 @@ if FEATURES['ENABLE_COURSEWARE_INDEX'] or FEATURES['ENABLE_LIBRARY_INDEX']:
|
||||
ELASTIC_SEARCH_CONFIG = ENV_TOKENS.get('ELASTIC_SEARCH_CONFIG', [{}])
|
||||
|
||||
XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {})
|
||||
XBLOCK_SETTINGS.setdefault("VideoDescriptor", {})["licensing_enabled"] = FEATURES.get("LICENSING", False)
|
||||
XBLOCK_SETTINGS.setdefault("VideoModule", {})['YOUTUBE_API_KEY'] = AUTH_TOKENS.get('YOUTUBE_API_KEY', YOUTUBE_API_KEY)
|
||||
XBLOCK_SETTINGS.setdefault("VideoBlock", {})["licensing_enabled"] = FEATURES.get("LICENSING", False)
|
||||
XBLOCK_SETTINGS.setdefault("VideoBlock", {})['YOUTUBE_API_KEY'] = AUTH_TOKENS.get('YOUTUBE_API_KEY', YOUTUBE_API_KEY)
|
||||
|
||||
################# MICROSITE ####################
|
||||
# microsite specific configurations.
|
||||
|
||||
@@ -116,7 +116,7 @@ div.wrapper-comp-editor.is-inactive ~ div.launch-latex-compiler {
|
||||
// ====================
|
||||
|
||||
// xmodule editor tab font-weight override
|
||||
.xmodule_edit.xmodule_VideoDescriptor .editor-with-tabs .editor-tabs .inner_tab_wrap a.tab {
|
||||
.xmodule_edit.xmodule_VideoBlock .editor-with-tabs .editor-tabs .inner_tab_wrap a.tab {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -424,7 +424,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.xmodule_edit.xmodule_VideoDescriptor .editor-with-tabs {
|
||||
.xmodule_edit.xmodule_VideoBlock .editor-with-tabs {
|
||||
.edit-header {
|
||||
border: 0;
|
||||
background-color: $gray-l4;
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.xmodule_VideoDescriptor {
|
||||
.xmodule_VideoBlock {
|
||||
.wrapper-comp-settings.basic_metadata_edit {
|
||||
.list-input.settings-list {
|
||||
.field.comp-setting-entry {
|
||||
|
||||
@@ -18,8 +18,6 @@ XMODULES = [
|
||||
"section = xmodule.backcompat_module:SemanticSectionDescriptor",
|
||||
"sequential = xmodule.seq_module:SequenceDescriptor",
|
||||
"slides = xmodule.backcompat_module:TranslateCustomTagDescriptor",
|
||||
"video = xmodule.video_module:VideoDescriptor",
|
||||
"videoalpha = xmodule.video_module:VideoDescriptor",
|
||||
"videodev = xmodule.backcompat_module:TranslateCustomTagDescriptor",
|
||||
"videosequence = xmodule.seq_module:SequenceDescriptor",
|
||||
"course_info = xmodule.html_module:CourseInfoDescriptor",
|
||||
@@ -36,6 +34,8 @@ XBLOCKS = [
|
||||
"library = xmodule.library_root_xblock:LibraryRoot",
|
||||
"problem = xmodule.capa_module:ProblemBlock",
|
||||
"vertical = xmodule.vertical_block:VerticalBlock",
|
||||
"video = xmodule.video_module:VideoBlock",
|
||||
"videoalpha = xmodule.video_module:VideoBlock",
|
||||
"wrapper = xmodule.wrapper_module:WrapperBlock",
|
||||
]
|
||||
XBLOCKS_ASIDES = [
|
||||
|
||||
@@ -51,17 +51,11 @@ class EditingDescriptor(EditingMixin, MakoModuleDescriptor):
|
||||
pass
|
||||
|
||||
|
||||
class TabsEditingDescriptor(EditingFields, MakoModuleDescriptor):
|
||||
class TabsEditingMixin(EditingFields, MakoTemplateBlockBase):
|
||||
"""
|
||||
Module that provides a raw editing view of its data and children. It does not
|
||||
perform any validation on its definition---just passes it along to the browser.
|
||||
|
||||
This class is intended to be used as a mixin.
|
||||
|
||||
Engine (module_edit.js) wants for metadata editor
|
||||
template to be always loaded, so don't forget to include
|
||||
settings tab in your module descriptor.
|
||||
Common code between TabsEditingDescriptor and XBlocks converted from XModules.
|
||||
"""
|
||||
|
||||
mako_template = "widgets/tabs-aggregator.html"
|
||||
css = {'scss': [resource_string(__name__, 'css/tabs/tabs.scss')]}
|
||||
js = {'js': [resource_string(
|
||||
@@ -70,7 +64,7 @@ class TabsEditingDescriptor(EditingFields, MakoModuleDescriptor):
|
||||
tabs = []
|
||||
|
||||
def get_context(self):
|
||||
_context = super(TabsEditingDescriptor, self).get_context()
|
||||
_context = MakoTemplateBlockBase.get_context(self)
|
||||
_context.update({
|
||||
'tabs': self.tabs,
|
||||
'html_id': self.location.html_id(), # element_id
|
||||
@@ -91,6 +85,20 @@ class TabsEditingDescriptor(EditingFields, MakoModuleDescriptor):
|
||||
return cls.css
|
||||
|
||||
|
||||
class TabsEditingDescriptor(TabsEditingMixin, MakoModuleDescriptor):
|
||||
"""
|
||||
Module that provides a raw editing view of its data and children. It does not
|
||||
perform any validation on its definition---just passes it along to the browser.
|
||||
|
||||
This class is intended to be used as a mixin.
|
||||
|
||||
Engine (module_edit.js) wants for metadata editor
|
||||
template to be always loaded, so don't forget to include
|
||||
settings tab in your module descriptor.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class XMLEditingDescriptor(EditingDescriptor):
|
||||
"""
|
||||
Module that provides a raw editing view of its data as XML. It does not perform
|
||||
|
||||
@@ -69,10 +69,9 @@ class RawDescriptor(RawMixin, XmlDescriptor, XMLEditingDescriptor):
|
||||
pass
|
||||
|
||||
|
||||
class EmptyDataRawDescriptor(XmlDescriptor, XMLEditingDescriptor):
|
||||
class EmptyDataRawMixin(object):
|
||||
"""
|
||||
Version of RawDescriptor for modules which may have no XML data,
|
||||
but use XMLEditingDescriptor for import/export handling.
|
||||
Common code between EmptyDataRawDescriptor and XBlocks converted from XModules.
|
||||
"""
|
||||
resources_dir = None
|
||||
|
||||
@@ -88,3 +87,11 @@ class EmptyDataRawDescriptor(XmlDescriptor, XMLEditingDescriptor):
|
||||
if self.data:
|
||||
return etree.fromstring(self.data)
|
||||
return etree.Element(self.category)
|
||||
|
||||
|
||||
class EmptyDataRawDescriptor(EmptyDataRawMixin, XmlDescriptor, XMLEditingDescriptor):
|
||||
"""
|
||||
Version of RawDescriptor for modules which may have no XML data,
|
||||
but use XMLEditingDescriptor for import/export handling.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -14,22 +14,57 @@ import os
|
||||
import sys
|
||||
import textwrap
|
||||
from collections import defaultdict
|
||||
from pkg_resources import resource_string
|
||||
|
||||
import django
|
||||
import six
|
||||
from docopt import docopt
|
||||
from path import Path as path
|
||||
from xmodule.x_module import XModuleDescriptor
|
||||
|
||||
from .capa_module import ProblemBlock
|
||||
from xmodule.capa_module import ProblemBlock
|
||||
from xmodule.x_module import XModuleDescriptor, HTMLSnippet
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VideoBlock(HTMLSnippet):
|
||||
"""
|
||||
Static assets for VideoBlock.
|
||||
Kept here because importing VideoBlock code requires Django to be setup.
|
||||
"""
|
||||
|
||||
preview_view_js = {
|
||||
'js': [
|
||||
resource_string(__name__, 'js/src/video/10_main.js'),
|
||||
],
|
||||
'xmodule_js': resource_string(__name__, 'js/src/xmodule.js')
|
||||
}
|
||||
preview_view_css = {
|
||||
'scss': [
|
||||
resource_string(__name__, 'css/video/display.scss'),
|
||||
resource_string(__name__, 'css/video/accessible_menu.scss'),
|
||||
],
|
||||
}
|
||||
|
||||
studio_view_js = {
|
||||
'js': [
|
||||
resource_string(__name__, 'js/src/tabs/tabs-aggregator.js'),
|
||||
],
|
||||
'xmodule_js': resource_string(__name__, 'js/src/xmodule.js'),
|
||||
}
|
||||
|
||||
studio_view_css = {
|
||||
'scss': [
|
||||
resource_string(__name__, 'css/tabs/tabs.scss'),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# List of XBlocks which use this static content setup.
|
||||
# Should only be used for XModules being converted to XBlocks.
|
||||
XBLOCK_CLASSES = [
|
||||
ProblemBlock,
|
||||
VideoBlock,
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -179,8 +179,8 @@ def mock_render_template(*args, **kwargs):
|
||||
class ModelsTest(unittest.TestCase):
|
||||
|
||||
def test_load_class(self):
|
||||
vc = XModuleDescriptor.load_class('video')
|
||||
vc_str = "<class 'xmodule.video_module.video_module.VideoDescriptor'>"
|
||||
vc = XModuleDescriptor.load_class('sequential')
|
||||
vc_str = "<class 'xmodule.seq_module.SequenceDescriptor'>"
|
||||
self.assertEqual(str(vc), vc_str)
|
||||
|
||||
|
||||
|
||||
@@ -36,9 +36,8 @@ from xblock.fields import ScopeIds
|
||||
|
||||
from xmodule.tests import get_test_descriptor_system
|
||||
from xmodule.validation import StudioValidationMessage
|
||||
from xmodule.video_module import VideoDescriptor, create_youtube_string, EXPORT_IMPORT_STATIC_DIR
|
||||
from xmodule.video_module import VideoBlock, create_youtube_string, EXPORT_IMPORT_STATIC_DIR
|
||||
from xmodule.video_module.transcripts_utils import download_youtube_subs, save_to_store, save_subs_to_store
|
||||
from . import LogicTest
|
||||
from .test_import import DummySystem
|
||||
|
||||
SRT_FILEDATA = '''
|
||||
@@ -96,11 +95,13 @@ def instantiate_descriptor(**field_data):
|
||||
"""
|
||||
Instantiate descriptor with most properties.
|
||||
"""
|
||||
if field_data.get('data', None):
|
||||
field_data = VideoBlock.parse_video_xml(field_data['data'])
|
||||
system = get_test_descriptor_system()
|
||||
course_key = CourseLocator('org', 'course', 'run')
|
||||
usage_key = course_key.make_usage_key('video', 'SampleProblem')
|
||||
return system.construct_xblock_from_class(
|
||||
VideoDescriptor,
|
||||
VideoBlock,
|
||||
scope_ids=ScopeIds(None, None, usage_key, usage_key),
|
||||
field_data=DictFieldData(field_data),
|
||||
)
|
||||
@@ -119,9 +120,8 @@ class _MockValCannotCreateError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class VideoModuleTest(LogicTest):
|
||||
"""Logic tests for Video Xmodule."""
|
||||
descriptor_class = VideoDescriptor
|
||||
class VideoBlockTest(unittest.TestCase):
|
||||
"""Logic tests for Video XBlock."""
|
||||
|
||||
raw_field_data = {
|
||||
'data': '<video />'
|
||||
@@ -130,7 +130,7 @@ class VideoModuleTest(LogicTest):
|
||||
def test_parse_youtube(self):
|
||||
"""Test parsing old-style Youtube ID strings into a dict."""
|
||||
youtube_str = '0.75:jNCf2gIqpeE,1.00:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg'
|
||||
output = VideoDescriptor._parse_youtube(youtube_str)
|
||||
output = VideoBlock._parse_youtube(youtube_str)
|
||||
self.assertEqual(output, {'0.75': 'jNCf2gIqpeE',
|
||||
'1.00': 'ZwkTiUPN0mg',
|
||||
'1.25': 'rsq9auxASqI',
|
||||
@@ -142,7 +142,7 @@ class VideoModuleTest(LogicTest):
|
||||
empty string.
|
||||
"""
|
||||
youtube_str = '0.75:jNCf2gIqpeE'
|
||||
output = VideoDescriptor._parse_youtube(youtube_str)
|
||||
output = VideoBlock._parse_youtube(youtube_str)
|
||||
self.assertEqual(output, {'0.75': 'jNCf2gIqpeE',
|
||||
'1.00': '',
|
||||
'1.25': '',
|
||||
@@ -152,14 +152,14 @@ class VideoModuleTest(LogicTest):
|
||||
"""Ensure that ids that are invalid return an empty dict"""
|
||||
# invalid id
|
||||
youtube_str = 'thisisaninvalidid'
|
||||
output = VideoDescriptor._parse_youtube(youtube_str)
|
||||
output = VideoBlock._parse_youtube(youtube_str)
|
||||
self.assertEqual(output, {'0.75': '',
|
||||
'1.00': '',
|
||||
'1.25': '',
|
||||
'1.50': ''})
|
||||
# another invalid id
|
||||
youtube_str = ',::,:,,'
|
||||
output = VideoDescriptor._parse_youtube(youtube_str)
|
||||
output = VideoBlock._parse_youtube(youtube_str)
|
||||
self.assertEqual(output, {'0.75': '',
|
||||
'1.00': '',
|
||||
'1.25': '',
|
||||
@@ -167,7 +167,7 @@ class VideoModuleTest(LogicTest):
|
||||
|
||||
# and another one, partially invalid
|
||||
youtube_str = '0.75_BAD!!!,1.0:AXdE34_U,1.25:KLHF9K_Y,1.5:VO3SxfeD,'
|
||||
output = VideoDescriptor._parse_youtube(youtube_str)
|
||||
output = VideoBlock._parse_youtube(youtube_str)
|
||||
self.assertEqual(output, {'0.75': '',
|
||||
'1.00': 'AXdE34_U',
|
||||
'1.25': 'KLHF9K_Y',
|
||||
@@ -180,8 +180,8 @@ class VideoModuleTest(LogicTest):
|
||||
youtube_str = '1.00:p2Q6BrNhdh8'
|
||||
youtube_str_hack = '1.0:p2Q6BrNhdh8'
|
||||
self.assertEqual(
|
||||
VideoDescriptor._parse_youtube(youtube_str),
|
||||
VideoDescriptor._parse_youtube(youtube_str_hack)
|
||||
VideoBlock._parse_youtube(youtube_str),
|
||||
VideoBlock._parse_youtube(youtube_str_hack)
|
||||
)
|
||||
|
||||
def test_parse_youtube_empty(self):
|
||||
@@ -190,7 +190,7 @@ class VideoModuleTest(LogicTest):
|
||||
that well.
|
||||
"""
|
||||
self.assertEqual(
|
||||
VideoDescriptor._parse_youtube(''),
|
||||
VideoBlock._parse_youtube(''),
|
||||
{'0.75': '',
|
||||
'1.00': '',
|
||||
'1.25': '',
|
||||
@@ -198,13 +198,13 @@ class VideoModuleTest(LogicTest):
|
||||
)
|
||||
|
||||
|
||||
class VideoDescriptorTestBase(unittest.TestCase):
|
||||
class VideoBlockTestBase(unittest.TestCase):
|
||||
"""
|
||||
Base class for tests for VideoDescriptor
|
||||
Base class for tests for VideoBlock
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(VideoDescriptorTestBase, self).setUp()
|
||||
super(VideoBlockTestBase, self).setUp()
|
||||
self.descriptor = instantiate_descriptor()
|
||||
|
||||
def assertXmlEqual(self, expected, xml):
|
||||
@@ -223,7 +223,7 @@ class VideoDescriptorTestBase(unittest.TestCase):
|
||||
self.assertXmlEqual(left, right)
|
||||
|
||||
|
||||
class TestCreateYoutubeString(VideoDescriptorTestBase):
|
||||
class TestCreateYoutubeString(VideoBlockTestBase):
|
||||
"""
|
||||
Checks that create_youtube_string correcty extracts information from Video descriptor.
|
||||
"""
|
||||
@@ -250,7 +250,7 @@ class TestCreateYoutubeString(VideoDescriptorTestBase):
|
||||
self.assertEqual(create_youtube_string(self.descriptor), expected)
|
||||
|
||||
|
||||
class TestCreateYouTubeUrl(VideoDescriptorTestBase):
|
||||
class TestCreateYouTubeUrl(VideoBlockTestBase):
|
||||
"""
|
||||
Tests for helper method `create_youtube_url`.
|
||||
"""
|
||||
@@ -264,9 +264,9 @@ class TestCreateYouTubeUrl(VideoDescriptorTestBase):
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class VideoDescriptorImportTestCase(TestCase):
|
||||
class VideoBlockImportTestCase(TestCase):
|
||||
"""
|
||||
Make sure that VideoDescriptor can import an old XML-based video correctly.
|
||||
Make sure that VideoBlock can import an old XML-based video correctly.
|
||||
"""
|
||||
|
||||
def assert_attributes_equal(self, video, attrs):
|
||||
@@ -328,7 +328,7 @@ class VideoDescriptorImportTestCase(TestCase):
|
||||
<transcript language="de" src="german_translation.srt" />
|
||||
</video>
|
||||
'''
|
||||
output = VideoDescriptor.from_xml(xml_data, module_system, Mock())
|
||||
output = VideoBlock.from_xml(xml_data, module_system, Mock())
|
||||
self.assert_attributes_equal(output, {
|
||||
'youtube_id_0_75': 'izygArpw-Qo',
|
||||
'youtube_id_1_0': 'p2Q6BrNhdh8',
|
||||
@@ -376,7 +376,7 @@ class VideoDescriptorImportTestCase(TestCase):
|
||||
id_generator = Mock()
|
||||
id_generator.target_course_id = course_id
|
||||
|
||||
output = VideoDescriptor.from_xml(xml_data, module_system, id_generator)
|
||||
output = VideoBlock.from_xml(xml_data, module_system, id_generator)
|
||||
self.assert_attributes_equal(output, {
|
||||
'youtube_id_0_75': 'izygArpw-Qo',
|
||||
'youtube_id_1_0': 'p2Q6BrNhdh8',
|
||||
@@ -407,7 +407,7 @@ class VideoDescriptorImportTestCase(TestCase):
|
||||
<source src="http://www.example.com/source.mp4"/>
|
||||
</video>
|
||||
'''
|
||||
output = VideoDescriptor.from_xml(xml_data, module_system, Mock())
|
||||
output = VideoBlock.from_xml(xml_data, module_system, Mock())
|
||||
self.assert_attributes_equal(output, {
|
||||
'youtube_id_0_75': '',
|
||||
'youtube_id_1_0': 'p2Q6BrNhdh8',
|
||||
@@ -419,7 +419,7 @@ class VideoDescriptorImportTestCase(TestCase):
|
||||
'track': '',
|
||||
'handout': None,
|
||||
'download_track': False,
|
||||
'download_video': True,
|
||||
'download_video': False,
|
||||
'html5_sources': ['http://www.example.com/source.mp4'],
|
||||
'data': ''
|
||||
})
|
||||
@@ -438,7 +438,7 @@ class VideoDescriptorImportTestCase(TestCase):
|
||||
<track src="http://www.example.com/track"/>
|
||||
</video>
|
||||
'''
|
||||
output = VideoDescriptor.from_xml(xml_data, module_system, Mock())
|
||||
output = VideoBlock.from_xml(xml_data, module_system, Mock())
|
||||
self.assert_attributes_equal(output, {
|
||||
'youtube_id_0_75': '',
|
||||
'youtube_id_1_0': 'p2Q6BrNhdh8',
|
||||
@@ -449,7 +449,7 @@ class VideoDescriptorImportTestCase(TestCase):
|
||||
'end_time': datetime.timedelta(seconds=0.0),
|
||||
'track': 'http://www.example.com/track',
|
||||
'download_track': True,
|
||||
'download_video': True,
|
||||
'download_video': False,
|
||||
'html5_sources': ['http://www.example.com/source.mp4'],
|
||||
'data': '',
|
||||
'transcripts': {},
|
||||
@@ -461,7 +461,7 @@ class VideoDescriptorImportTestCase(TestCase):
|
||||
"""
|
||||
module_system = DummySystem(load_error_modules=True)
|
||||
xml_data = '<video></video>'
|
||||
output = VideoDescriptor.from_xml(xml_data, module_system, Mock())
|
||||
output = VideoBlock.from_xml(xml_data, module_system, Mock())
|
||||
self.assert_attributes_equal(output, {
|
||||
'youtube_id_0_75': '',
|
||||
'youtube_id_1_0': '3_yD_cEKoCk',
|
||||
@@ -500,7 +500,7 @@ class VideoDescriptorImportTestCase(TestCase):
|
||||
youtube_id_1_0=""OEoXaMPEzf10""
|
||||
/>
|
||||
'''
|
||||
output = VideoDescriptor.from_xml(xml_data, module_system, Mock())
|
||||
output = VideoBlock.from_xml(xml_data, module_system, Mock())
|
||||
self.assert_attributes_equal(output, {
|
||||
'youtube_id_0_75': 'OEoXaMPEzf65',
|
||||
'youtube_id_1_0': 'OEoXaMPEzf10',
|
||||
@@ -524,7 +524,7 @@ class VideoDescriptorImportTestCase(TestCase):
|
||||
youtube="1.0:"p2Q6BrNhdh8",1.25:"1EeWXzPdhSA"">
|
||||
</video>
|
||||
'''
|
||||
output = VideoDescriptor.from_xml(xml_data, module_system, Mock())
|
||||
output = VideoBlock.from_xml(xml_data, module_system, Mock())
|
||||
self.assert_attributes_equal(output, {
|
||||
'youtube_id_0_75': '',
|
||||
'youtube_id_1_0': 'p2Q6BrNhdh8',
|
||||
@@ -543,7 +543,7 @@ class VideoDescriptorImportTestCase(TestCase):
|
||||
|
||||
def test_old_video_format(self):
|
||||
"""
|
||||
Test backwards compatibility with VideoModule's XML format.
|
||||
Test backwards compatibility with VideoBlock's XML format.
|
||||
"""
|
||||
module_system = DummySystem(load_error_modules=True)
|
||||
xml_data = """
|
||||
@@ -557,7 +557,7 @@ class VideoDescriptorImportTestCase(TestCase):
|
||||
<track src="http://www.example.com/track"/>
|
||||
</video>
|
||||
"""
|
||||
output = VideoDescriptor.from_xml(xml_data, module_system, Mock())
|
||||
output = VideoBlock.from_xml(xml_data, module_system, Mock())
|
||||
self.assert_attributes_equal(output, {
|
||||
'youtube_id_0_75': 'izygArpw-Qo',
|
||||
'youtube_id_1_0': 'p2Q6BrNhdh8',
|
||||
@@ -574,7 +574,7 @@ class VideoDescriptorImportTestCase(TestCase):
|
||||
|
||||
def test_old_video_data(self):
|
||||
"""
|
||||
Ensure that Video is able to read VideoModule's model data.
|
||||
Ensure that Video is able to read VideoBlock's model data.
|
||||
"""
|
||||
module_system = DummySystem(load_error_modules=True)
|
||||
xml_data = """
|
||||
@@ -587,7 +587,7 @@ class VideoDescriptorImportTestCase(TestCase):
|
||||
<track src="http://www.example.com/track"/>
|
||||
</video>
|
||||
"""
|
||||
video = VideoDescriptor.from_xml(xml_data, module_system, Mock())
|
||||
video = VideoBlock.from_xml(xml_data, module_system, Mock())
|
||||
self.assert_attributes_equal(video, {
|
||||
'youtube_id_0_75': 'izygArpw-Qo',
|
||||
'youtube_id_1_0': 'p2Q6BrNhdh8',
|
||||
@@ -604,7 +604,7 @@ class VideoDescriptorImportTestCase(TestCase):
|
||||
|
||||
def test_import_with_float_times(self):
|
||||
"""
|
||||
Ensure that Video is able to read VideoModule's model data.
|
||||
Ensure that Video is able to read VideoBlock's model data.
|
||||
"""
|
||||
module_system = DummySystem(load_error_modules=True)
|
||||
xml_data = """
|
||||
@@ -617,7 +617,7 @@ class VideoDescriptorImportTestCase(TestCase):
|
||||
<track src="http://www.example.com/track"/>
|
||||
</video>
|
||||
"""
|
||||
video = VideoDescriptor.from_xml(xml_data, module_system, Mock())
|
||||
video = VideoBlock.from_xml(xml_data, module_system, Mock())
|
||||
self.assert_attributes_equal(video, {
|
||||
'youtube_id_0_75': 'izygArpw-Qo',
|
||||
'youtube_id_1_0': 'p2Q6BrNhdh8',
|
||||
@@ -665,7 +665,7 @@ class VideoDescriptorImportTestCase(TestCase):
|
||||
)
|
||||
id_generator = Mock()
|
||||
id_generator.target_course_id = 'test_course_id'
|
||||
video = VideoDescriptor.from_xml(xml_data, module_system, id_generator)
|
||||
video = VideoBlock.from_xml(xml_data, module_system, id_generator)
|
||||
|
||||
self.assert_attributes_equal(video, {'edx_video_id': edx_video_id})
|
||||
mock_val_api.import_from_xml.assert_called_once_with(
|
||||
@@ -690,12 +690,12 @@ class VideoDescriptorImportTestCase(TestCase):
|
||||
</video>
|
||||
"""
|
||||
with self.assertRaises(mock_val_api.ValCannotCreateError):
|
||||
VideoDescriptor.from_xml(xml_data, module_system, id_generator=Mock())
|
||||
VideoBlock.from_xml(xml_data, module_system, id_generator=Mock())
|
||||
|
||||
|
||||
class VideoExportTestCase(VideoDescriptorTestBase):
|
||||
class VideoExportTestCase(VideoBlockTestBase):
|
||||
"""
|
||||
Make sure that VideoDescriptor can export itself to XML correctly.
|
||||
Make sure that VideoBlock can export itself to XML correctly.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
@@ -773,7 +773,7 @@ class VideoExportTestCase(VideoDescriptorTestBase):
|
||||
|
||||
xml = self.descriptor.definition_to_xml(self.file_system)
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
xml_string = '<video url_name="SampleProblem" download_video="false"/>'
|
||||
xml_string = '<video url_name="SampleProblem"/>'
|
||||
expected = etree.XML(xml_string, parser=parser)
|
||||
self.assertXmlEqual(expected, xml)
|
||||
|
||||
@@ -813,7 +813,7 @@ class VideoExportTestCase(VideoDescriptorTestBase):
|
||||
"""
|
||||
xml = self.descriptor.definition_to_xml(self.file_system)
|
||||
# Check that download_video field is also set to default (False) in xml for backward compatibility
|
||||
expected = '<video url_name="SampleProblem" download_video="false"/>\n'
|
||||
expected = '<video url_name="SampleProblem"/>\n'
|
||||
self.assertEquals(expected, etree.tostring(xml, pretty_print=True))
|
||||
|
||||
@patch('xmodule.video_module.video_module.edxval_api', None)
|
||||
@@ -823,7 +823,7 @@ class VideoExportTestCase(VideoDescriptorTestBase):
|
||||
"""
|
||||
self.descriptor.transcripts = None
|
||||
xml = self.descriptor.definition_to_xml(self.file_system)
|
||||
expected = '<video url_name="SampleProblem" download_video="false"/>\n'
|
||||
expected = '<video url_name="SampleProblem"/>\n'
|
||||
self.assertEquals(expected, etree.tostring(xml, pretty_print=True))
|
||||
|
||||
@patch('xmodule.video_module.video_module.edxval_api', None)
|
||||
@@ -850,9 +850,9 @@ class VideoExportTestCase(VideoDescriptorTestBase):
|
||||
@patch.object(settings, 'FEATURES', create=True, new={
|
||||
'FALLBACK_TO_ENGLISH_TRANSCRIPTS': False,
|
||||
})
|
||||
class VideoDescriptorStudentViewDataTestCase(unittest.TestCase):
|
||||
class VideoBlockStudentViewDataTestCase(unittest.TestCase):
|
||||
"""
|
||||
Make sure that VideoDescriptor returns the expected student_view_data.
|
||||
Make sure that VideoBlock returns the expected student_view_data.
|
||||
"""
|
||||
|
||||
VIDEO_URL_1 = 'http://www.example.com/source_low.mp4'
|
||||
@@ -865,41 +865,6 @@ class VideoDescriptorStudentViewDataTestCase(unittest.TestCase):
|
||||
{'only_on_web': True},
|
||||
{'only_on_web': True},
|
||||
),
|
||||
# Ensure that the deprecated `source` attribute is included in the `all_sources` list.
|
||||
(
|
||||
{
|
||||
'only_on_web': False,
|
||||
'youtube_id_1_0': None,
|
||||
'source': VIDEO_URL_1,
|
||||
},
|
||||
{
|
||||
'only_on_web': False,
|
||||
'duration': None,
|
||||
'transcripts': {},
|
||||
'encoded_videos': {
|
||||
'fallback': {'url': VIDEO_URL_1, 'file_size': 0},
|
||||
},
|
||||
'all_sources': [VIDEO_URL_1],
|
||||
},
|
||||
),
|
||||
# Ensure that `html5_sources` take precendence over deprecated `source` url
|
||||
(
|
||||
{
|
||||
'only_on_web': False,
|
||||
'youtube_id_1_0': None,
|
||||
'source': VIDEO_URL_1,
|
||||
'html5_sources': [VIDEO_URL_2, VIDEO_URL_3],
|
||||
},
|
||||
{
|
||||
'only_on_web': False,
|
||||
'duration': None,
|
||||
'transcripts': {},
|
||||
'encoded_videos': {
|
||||
'fallback': {'url': VIDEO_URL_2, 'file_size': 0},
|
||||
},
|
||||
'all_sources': [VIDEO_URL_2, VIDEO_URL_3, VIDEO_URL_1],
|
||||
},
|
||||
),
|
||||
# Ensure that YouTube URLs are included in `encoded_videos`, but not `all_sources`.
|
||||
(
|
||||
{
|
||||
@@ -1002,9 +967,9 @@ class VideoDescriptorStudentViewDataTestCase(unittest.TestCase):
|
||||
# The default value in {lms,cms}/envs/common.py and xmodule/tests/test_video.py should be consistent.
|
||||
'FALLBACK_TO_ENGLISH_TRANSCRIPTS': True,
|
||||
})
|
||||
class VideoDescriptorIndexingTestCase(unittest.TestCase):
|
||||
class VideoBlockIndexingTestCase(unittest.TestCase):
|
||||
"""
|
||||
Make sure that VideoDescriptor can format data for indexing as expected.
|
||||
Make sure that VideoBlock can format data for indexing as expected.
|
||||
"""
|
||||
|
||||
def test_video_with_no_subs_index_dictionary(self):
|
||||
|
||||
@@ -34,7 +34,6 @@ from xmodule.course_module import CourseDescriptor
|
||||
from xmodule.html_module import HtmlDescriptor
|
||||
from xmodule.poll_module import PollDescriptor
|
||||
from xmodule.word_cloud_module import WordCloudDescriptor
|
||||
#from xmodule.video_module import VideoDescriptor
|
||||
from xmodule.seq_module import SequenceDescriptor
|
||||
from xmodule.conditional_module import ConditionalDescriptor
|
||||
from xmodule.randomize_module import RandomizeDescriptor
|
||||
@@ -51,8 +50,6 @@ LEAF_XMODULES = {
|
||||
HtmlDescriptor: [{}],
|
||||
PollDescriptor: [{'display_name': 'Poll Display Name'}],
|
||||
WordCloudDescriptor: [{}],
|
||||
# This is being excluded because it has dependencies on django
|
||||
#VideoDescriptor,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ def bumper_metadata(video, sources):
|
||||
unused_track_url, bumper_transcript_language, bumper_languages = video.get_transcripts_for_student(transcripts)
|
||||
|
||||
metadata = OrderedDict({
|
||||
'saveStateUrl': video.system.ajax_url + '/save_user_state',
|
||||
'saveStateUrl': video.ajax_url + '/save_user_state',
|
||||
'showCaptions': json.dumps(video.show_captions),
|
||||
'sources': sources,
|
||||
'streams': '',
|
||||
|
||||
@@ -730,7 +730,7 @@ class Transcript(object):
|
||||
class VideoTranscriptsMixin(object):
|
||||
"""Mixin class for transcript functionality.
|
||||
|
||||
This is necessary for both VideoModule and VideoDescriptor.
|
||||
This is necessary for VideoBlock.
|
||||
"""
|
||||
|
||||
def available_translations(self, transcripts, verify_assets=None, is_bumper=False):
|
||||
@@ -740,7 +740,7 @@ class VideoTranscriptsMixin(object):
|
||||
Arguments:
|
||||
verify_assets (boolean): If True, checks to ensure that the transcripts
|
||||
really exist in the contentstore. If False, we just look at the
|
||||
VideoDescriptor fields and do not query the contentstore. One reason
|
||||
VideoBlock fields and do not query the contentstore. One reason
|
||||
we might do this is to avoid slamming contentstore() with queries
|
||||
when trying to make a listing of videos and their languages.
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ class VideoStudentViewHandlers(object):
|
||||
if transcript_name:
|
||||
# Get the asset path for course
|
||||
asset_path = None
|
||||
course = self.descriptor.runtime.modulestore.get_course(self.course_id)
|
||||
course = self.runtime.modulestore.get_course(self.course_id)
|
||||
if course.static_asset_path:
|
||||
asset_path = course.static_asset_path
|
||||
else:
|
||||
|
||||
@@ -24,7 +24,6 @@ import six
|
||||
from django.conf import settings
|
||||
from lxml import etree
|
||||
from opaque_keys.edx.locator import AssetLocator
|
||||
from pkg_resources import resource_string
|
||||
from web_fragments.fragment import Fragment
|
||||
from xblock.completable import XBlockCompletionMode
|
||||
from xblock.core import XBlock
|
||||
@@ -36,14 +35,19 @@ from openedx.core.djangoapps.video_pipeline.config.waffle import DEPRECATE_YOUTU
|
||||
from openedx.core.lib.cache_utils import request_cached
|
||||
from openedx.core.lib.license import LicenseMixin
|
||||
from xmodule.contentstore.content import StaticContent
|
||||
from xmodule.editing_module import TabsEditingDescriptor
|
||||
from xmodule.editing_module import EditingMixin, TabsEditingMixin
|
||||
from xmodule.exceptions import NotFoundError
|
||||
from xmodule.modulestore.inheritance import InheritanceKeyValueStore, own_metadata
|
||||
from xmodule.raw_module import EmptyDataRawDescriptor
|
||||
from xmodule.raw_module import EmptyDataRawMixin
|
||||
from xmodule.validation import StudioValidation, StudioValidationMessage
|
||||
from xmodule.util.xmodule_django import add_webpack_to_fragment
|
||||
from xmodule.video_module import manage_video_subtitles_save
|
||||
from xmodule.x_module import PUBLIC_VIEW, STUDENT_VIEW, XModule, module_attr
|
||||
from xmodule.xml_module import deserialize_field, is_pointer_tag, name_to_pathname
|
||||
from xmodule.x_module import (
|
||||
PUBLIC_VIEW, STUDENT_VIEW,
|
||||
HTMLSnippet, ResourceTemplates, shim_xmodule_js,
|
||||
XModuleMixin, XModuleToXBlockMixin, XModuleDescriptorToXBlockMixin,
|
||||
)
|
||||
from xmodule.xml_module import XmlMixin, deserialize_field, is_pointer_tag, name_to_pathname
|
||||
|
||||
from .bumper_utils import bumperize
|
||||
from .transcripts_utils import (
|
||||
@@ -61,25 +65,25 @@ from .video_xfields import VideoFields
|
||||
# The following import/except block for edxval is temporary measure until
|
||||
# edxval is a proper XBlock Runtime Service.
|
||||
#
|
||||
# Here's the deal: the VideoModule should be able to take advantage of edx-val
|
||||
# Here's the deal: the VideoBlock should be able to take advantage of edx-val
|
||||
# (https://github.com/edx/edx-val) to figure out what URL to give for video
|
||||
# resources that have an edx_video_id specified. edx-val is a Django app, and
|
||||
# including it causes tests to fail because we run common/lib tests standalone
|
||||
# without Django dependencies. The alternatives seem to be:
|
||||
#
|
||||
# 1. Move VideoModule out of edx-platform.
|
||||
# 1. Move VideoBlock out of edx-platform.
|
||||
# 2. Accept the Django dependency in common/lib.
|
||||
# 3. Try to import, catch the exception on failure, and check for the existence
|
||||
# of edxval_api before invoking it in the code.
|
||||
# 4. Make edxval an XBlock Runtime Service
|
||||
#
|
||||
# (1) is a longer term goal. VideoModule should be made into an XBlock and
|
||||
# (1) is a longer term goal. VideoBlock should be made into an XBlock and
|
||||
# extracted from edx-platform entirely. But that's expensive to do because of
|
||||
# the various dependencies (like templates). Need to sort this out.
|
||||
# (2) is explicitly discouraged.
|
||||
# (3) is what we're doing today. The code is still functional when called within
|
||||
# the context of the LMS, but does not cause failure on import when running
|
||||
# standalone tests. Most VideoModule tests tend to be in the LMS anyway,
|
||||
# standalone tests. Most VideoBlock tests tend to be in the LMS anyway,
|
||||
# probably for historical reasons, so we're not making things notably worse.
|
||||
# (4) is one of the next items on the backlog for edxval, and should get rid
|
||||
# of this particular import silliness. It's just that I haven't made one before,
|
||||
@@ -104,8 +108,12 @@ EXPORT_IMPORT_COURSE_DIR = u'course'
|
||||
EXPORT_IMPORT_STATIC_DIR = u'static'
|
||||
|
||||
|
||||
@XBlock.wants('settings', 'completion')
|
||||
class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers, XModule, LicenseMixin):
|
||||
@XBlock.wants('settings', 'completion', 'i18n', 'request_cache')
|
||||
class VideoBlock(
|
||||
VideoFields, VideoTranscriptsMixin, VideoStudioViewHandlers, VideoStudentViewHandlers,
|
||||
TabsEditingMixin, EmptyDataRawMixin, XmlMixin, EditingMixin,
|
||||
XModuleDescriptorToXBlockMixin, XModuleToXBlockMixin, HTMLSnippet, ResourceTemplates, XModuleMixin,
|
||||
LicenseMixin):
|
||||
"""
|
||||
XML source example:
|
||||
<video show_captions="true"
|
||||
@@ -123,27 +131,22 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
|
||||
video_time = 0
|
||||
icon_class = 'video'
|
||||
|
||||
# To make sure that js files are called in proper order we use numerical
|
||||
# index. We do that to avoid issues that occurs in tests.
|
||||
module = __name__.replace('.video_module', '', 2)
|
||||
show_in_read_only_mode = True
|
||||
|
||||
#TODO: For each of the following, ensure that any generated html is properly escaped.
|
||||
js = {
|
||||
'js': [
|
||||
resource_string(module, 'js/src/video/10_main.js'),
|
||||
]
|
||||
}
|
||||
css = {'scss': [
|
||||
resource_string(module, 'css/video/display.scss'),
|
||||
resource_string(module, 'css/video/accessible_menu.scss'),
|
||||
]}
|
||||
js_module_name = "Video"
|
||||
tabs = [
|
||||
{
|
||||
'name': _("Basic"),
|
||||
'template': "video/transcripts.html",
|
||||
'current': True
|
||||
},
|
||||
{
|
||||
'name': _("Advanced"),
|
||||
'template': "tabs/metadata-edit-tab.html"
|
||||
}
|
||||
]
|
||||
|
||||
def validate(self):
|
||||
"""
|
||||
Validates the state of this Video Module Instance.
|
||||
"""
|
||||
return self.descriptor.validate()
|
||||
uses_xmodule_styles_setup = True
|
||||
requires_per_student_anonymous_id = True
|
||||
|
||||
def get_transcripts_for_student(self, transcripts):
|
||||
"""Return transcript information necessary for rendering the XModule student view.
|
||||
@@ -211,6 +214,32 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
|
||||
|
||||
return False
|
||||
|
||||
def student_view(self, _context):
|
||||
"""
|
||||
Return the student view.
|
||||
"""
|
||||
fragment = Fragment(self.get_html())
|
||||
add_webpack_to_fragment(fragment, 'VideoBlockPreview')
|
||||
shim_xmodule_js(fragment, 'Video')
|
||||
return fragment
|
||||
|
||||
def author_view(self, context):
|
||||
"""
|
||||
Renders the Studio preview view.
|
||||
"""
|
||||
return self.student_view(context)
|
||||
|
||||
def studio_view(self, _context):
|
||||
"""
|
||||
Return the studio view.
|
||||
"""
|
||||
fragment = Fragment(
|
||||
self.system.render_template(self.mako_template, self.get_context())
|
||||
)
|
||||
add_webpack_to_fragment(fragment, 'VideoBlockStudio')
|
||||
shim_xmodule_js(fragment, 'TabsEditingDescriptor')
|
||||
return fragment
|
||||
|
||||
def public_view(self, context):
|
||||
"""
|
||||
Returns a fragment that contains the html for the public view
|
||||
@@ -279,7 +308,7 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
|
||||
except (edxval_api.ValInternalError, edxval_api.ValVideoNotFoundError):
|
||||
# VAL raises this exception if it can't find data for the edx video ID. This can happen if the
|
||||
# course data is ported to a machine that does not have the VAL data. So for now, pass on this
|
||||
# exception and fallback to whatever we find in the VideoDescriptor.
|
||||
# exception and fallback to whatever we find in the VideoBlock.
|
||||
log.warning("Could not retrieve information from VAL for edx Video ID: %s.", self.edx_video_id)
|
||||
|
||||
# If the user comes from China use China CDN for html5 videos.
|
||||
@@ -296,11 +325,9 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
|
||||
sources[index] = new_url
|
||||
|
||||
# If there was no edx_video_id, or if there was no download specified
|
||||
# for it, we fall back on whatever we find in the VideoDescriptor
|
||||
# for it, we fall back on whatever we find in the VideoBlock.
|
||||
if not download_video_link and self.download_video:
|
||||
if self.source:
|
||||
download_video_link = self.source
|
||||
elif self.html5_sources:
|
||||
if self.html5_sources:
|
||||
download_video_link = self.html5_sources[0]
|
||||
|
||||
# don't give the option to download HLS video urls
|
||||
@@ -351,7 +378,7 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
|
||||
|
||||
metadata = {
|
||||
'saveStateEnabled': view != PUBLIC_VIEW,
|
||||
'saveStateUrl': self.system.ajax_url + '/save_user_state',
|
||||
'saveStateUrl': self.ajax_url + '/save_user_state',
|
||||
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', False),
|
||||
'streams': self.youtube_streams,
|
||||
'sources': sources,
|
||||
@@ -421,85 +448,18 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
|
||||
'download_video_link': download_video_link,
|
||||
'track': track_url,
|
||||
'transcript_download_format': transcript_download_format,
|
||||
'transcript_download_formats_list': self.descriptor.fields['transcript_download_format'].values,
|
||||
'transcript_download_formats_list': self.fields['transcript_download_format'].values,
|
||||
'license': getattr(self, "license", None),
|
||||
}
|
||||
return self.system.render_template('video.html', context)
|
||||
|
||||
|
||||
@XBlock.wants("request_cache", "settings", "completion")
|
||||
class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandlers,
|
||||
TabsEditingDescriptor, EmptyDataRawDescriptor, LicenseMixin):
|
||||
"""
|
||||
Descriptor for `VideoModule`.
|
||||
"""
|
||||
module_class = VideoModule
|
||||
transcript = module_attr('transcript')
|
||||
publish_completion = module_attr('publish_completion')
|
||||
has_custom_completion = module_attr('has_custom_completion')
|
||||
|
||||
show_in_read_only_mode = True
|
||||
|
||||
tabs = [
|
||||
{
|
||||
'name': _("Basic"),
|
||||
'template': "video/transcripts.html",
|
||||
'current': True
|
||||
},
|
||||
{
|
||||
'name': _("Advanced"),
|
||||
'template': "tabs/metadata-edit-tab.html"
|
||||
}
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Mostly handles backward compatibility issues.
|
||||
`source` is deprecated field.
|
||||
a) If `source` exists and `source` is not `html5_sources`: show `source`
|
||||
field on front-end as not-editable but clearable. Dropdown is a new
|
||||
field `download_video` and it has value True.
|
||||
b) If `source` is cleared it is not shown anymore.
|
||||
c) If `source` exists and `source` in `html5_sources`, do not show `source`
|
||||
field. `download_video` field has value True.
|
||||
"""
|
||||
super(VideoDescriptor, self).__init__(*args, **kwargs)
|
||||
# For backwards compatibility -- if we've got XML data, parse it out and set the metadata fields
|
||||
if self.data:
|
||||
field_data = self._parse_video_xml(etree.fromstring(self.data))
|
||||
self._field_data.set_many(self, field_data)
|
||||
del self.data
|
||||
|
||||
self.source_visible = False
|
||||
if self.source:
|
||||
# If `source` field value exist in the `html5_sources` field values,
|
||||
# then delete `source` field value and use value from `html5_sources` field.
|
||||
if self.source in self.html5_sources:
|
||||
self.source = '' # Delete source field value.
|
||||
self.download_video = True
|
||||
else: # Otherwise, `source` field value will be used.
|
||||
self.source_visible = True
|
||||
if not self.fields['download_video'].is_set_on(self):
|
||||
self.download_video = True
|
||||
|
||||
# Force download_video field to default value if it's not explicitly set for backward compatibility.
|
||||
if not self.fields['download_video'].is_set_on(self):
|
||||
self.download_video = self.download_video
|
||||
self.force_save_fields(['download_video'])
|
||||
|
||||
# for backward compatibility.
|
||||
# If course was existed and was not re-imported by the moment of adding `download_track` field,
|
||||
# we should enable `download_track` if following is true:
|
||||
if not self.fields['download_track'].is_set_on(self) and self.track:
|
||||
self.download_track = True
|
||||
|
||||
def validate(self):
|
||||
"""
|
||||
Validates the state of this video Module Instance. This
|
||||
Validates the state of this Video XBlock instance. This
|
||||
is the override of the general XBlock method, and it will also ask
|
||||
its superclass to validate.
|
||||
"""
|
||||
validation = super(VideoDescriptor, self).validate()
|
||||
validation = super(VideoBlock, self).validate()
|
||||
if not isinstance(validation, StudioValidation):
|
||||
validation = StudioValidation.copy(validation)
|
||||
|
||||
@@ -585,7 +545,7 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
|
||||
|
||||
@property
|
||||
def editable_metadata_fields(self):
|
||||
editable_fields = super(VideoDescriptor, self).editable_metadata_fields
|
||||
editable_fields = super(VideoBlock, self).editable_metadata_fields
|
||||
|
||||
settings_service = self.runtime.service(self, 'settings')
|
||||
if settings_service:
|
||||
@@ -593,11 +553,6 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
|
||||
if not xb_settings.get("licensing_enabled", False) and "license" in editable_fields:
|
||||
del editable_fields["license"]
|
||||
|
||||
if self.source_visible:
|
||||
editable_fields['source']['non_editable'] = True
|
||||
else:
|
||||
editable_fields.pop('source')
|
||||
|
||||
# Default Timed Transcript a.k.a `sub` has been deprecated and end users shall
|
||||
# not be able to modify it.
|
||||
editable_fields.pop('sub')
|
||||
@@ -659,7 +614,7 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
|
||||
filepath = cls._format_filepath(xml_object.tag, name_to_pathname(url_name))
|
||||
xml_object = cls.load_file(filepath, system.resources_fs, usage_id)
|
||||
system.parse_asides(xml_object, definition_id, usage_id, id_generator)
|
||||
field_data = cls._parse_video_xml(xml_object, id_generator)
|
||||
field_data = cls.parse_video_xml(xml_object, id_generator)
|
||||
kvs = InheritanceKeyValueStore(initial_values=field_data)
|
||||
field_data = KvsFieldData(kvs)
|
||||
video = system.construct_xblock_from_class(
|
||||
@@ -802,7 +757,7 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
|
||||
"""
|
||||
Extend context by data for transcript basic tab.
|
||||
"""
|
||||
_context = super(VideoDescriptor, self).get_context()
|
||||
_context = super(VideoBlock, self).get_context()
|
||||
|
||||
metadata_fields = copy.deepcopy(self.editable_metadata_fields)
|
||||
|
||||
@@ -896,7 +851,7 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
|
||||
return ret
|
||||
|
||||
@classmethod
|
||||
def _parse_video_xml(cls, xml, id_generator=None):
|
||||
def parse_video_xml(cls, xml, id_generator=None):
|
||||
"""
|
||||
Parse video fields out of xml_data. The fields are set if they are
|
||||
present in the XML.
|
||||
@@ -904,6 +859,9 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
|
||||
Arguments:
|
||||
id_generator is used to generate course-specific urls and identifiers
|
||||
"""
|
||||
if isinstance(xml, str) or isinstance(xml, unicode):
|
||||
xml = etree.fromstring(xml)
|
||||
|
||||
field_data = {}
|
||||
|
||||
# Convert between key types for certain attributes --
|
||||
@@ -1026,7 +984,7 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
|
||||
return edx_video_id
|
||||
|
||||
def index_dictionary(self):
|
||||
xblock_body = super(VideoDescriptor, self).index_dictionary()
|
||||
xblock_body = super(VideoBlock, self).index_dictionary()
|
||||
video_body = {
|
||||
"display_name": self.display_name,
|
||||
}
|
||||
@@ -1092,10 +1050,6 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
|
||||
val_video_data = {}
|
||||
all_sources = self.html5_sources or []
|
||||
|
||||
# `source` is a deprecated field, but we include it for backwards compatibility.
|
||||
if self.source:
|
||||
all_sources.append(self.source)
|
||||
|
||||
# Check in VAL data first if edx_video_id exists
|
||||
if self.edx_video_id:
|
||||
video_profile_names = context.get("profiles", ["mobile_low"])
|
||||
|
||||
@@ -103,7 +103,7 @@ def get_poster(video):
|
||||
|
||||
def format_xml_exception_message(location, key, value):
|
||||
"""
|
||||
Generate exception message for VideoDescriptor class which will use for ValueError and UnicodeDecodeError
|
||||
Generate exception message for VideoBlock class which will use for ValueError and UnicodeDecodeError
|
||||
when setting xml attributes.
|
||||
"""
|
||||
exception_message = "Block-location:{location}, Key:{key}, Value:{value}".format(
|
||||
|
||||
@@ -15,7 +15,7 @@ _ = lambda text: text
|
||||
|
||||
|
||||
class VideoFields(object):
|
||||
"""Fields for `VideoModule` and `VideoDescriptor`."""
|
||||
"""Fields for `VideoBlock`."""
|
||||
display_name = String(
|
||||
help=_("The display name for this component."),
|
||||
display_name=_("Component Display Name"),
|
||||
@@ -76,14 +76,6 @@ class VideoFields(object):
|
||||
)
|
||||
#front-end code of video player checks logical validity of (start_time, end_time) pair.
|
||||
|
||||
# `source` is deprecated field and should not be used in future.
|
||||
# `download_video` is used instead.
|
||||
source = String(
|
||||
help=_("The external URL to download the video."),
|
||||
display_name=_("Download Video"),
|
||||
scope=Scope.settings,
|
||||
default=""
|
||||
)
|
||||
download_video = Boolean(
|
||||
help=_("Allow students to download versions of this video in different formats if they cannot use the edX video player or do not have access to YouTube. You must add at least one non-YouTube URL in the Video File URLs field."), # pylint: disable=line-too-long
|
||||
display_name=_("Video Download Allowed"),
|
||||
|
||||
@@ -43,7 +43,7 @@ CSS_CLASS_NAMES = {
|
||||
'video_container': '.video',
|
||||
'video_sources': '.video-player video source',
|
||||
'video_spinner': '.video-wrapper .spinner',
|
||||
'video_xmodule': '.xmodule_VideoModule',
|
||||
'video_xmodule': '.xmodule_VideoBlock',
|
||||
'video_init': '.is-initialized',
|
||||
'video_time': '.vidtime',
|
||||
'video_display_name': '.vert h3',
|
||||
|
||||
@@ -17,7 +17,7 @@ from common.test.acceptance.tests.helpers import YouTubeStubConfig
|
||||
CLASS_SELECTORS = {
|
||||
'video_container': '.video',
|
||||
'video_init': '.is-initialized',
|
||||
'video_xmodule': '.xmodule_VideoModule',
|
||||
'video_xmodule': '.xmodule_VideoBlock',
|
||||
'video_spinner': '.video-wrapper .spinner',
|
||||
'video_controls': '.video-controls',
|
||||
'attach_asset': '.upload-dialog > input[type="file"]',
|
||||
|
||||
@@ -57,7 +57,7 @@ class VideoLicenseTest(StudioCourseTest):
|
||||
self.lms_courseware.visit()
|
||||
video = self.lms_courseware.q(css=".vert .xblock .video")
|
||||
self.assertTrue(video.is_present())
|
||||
video_license = self.lms_courseware.q(css=".vert .xblock.xmodule_VideoModule .xblock-license")
|
||||
video_license = self.lms_courseware.q(css=".vert .xblock.xmodule_VideoBlock .xblock-license")
|
||||
self.assertFalse(video_license.is_present())
|
||||
|
||||
def test_arr_license(self):
|
||||
@@ -83,7 +83,7 @@ class VideoLicenseTest(StudioCourseTest):
|
||||
self.lms_courseware.visit()
|
||||
video = self.lms_courseware.q(css=".vert .xblock .video")
|
||||
self.assertTrue(video.is_present())
|
||||
video_license_css = ".vert .xblock.xmodule_VideoModule .xblock-license"
|
||||
video_license_css = ".vert .xblock.xmodule_VideoBlock .xblock-license"
|
||||
self.lms_courseware.wait_for_element_presence(
|
||||
video_license_css, "Video module license block is present"
|
||||
)
|
||||
@@ -113,7 +113,7 @@ class VideoLicenseTest(StudioCourseTest):
|
||||
self.lms_courseware.visit()
|
||||
video = self.lms_courseware.q(css=".vert .xblock .video")
|
||||
self.assertTrue(video.is_present())
|
||||
video_license_css = ".vert .xblock.xmodule_VideoModule .xblock-license"
|
||||
video_license_css = ".vert .xblock.xmodule_VideoBlock .xblock-license"
|
||||
self.lms_courseware.wait_for_element_presence(
|
||||
video_license_css, "Video module license block is present"
|
||||
)
|
||||
|
||||
@@ -1013,7 +1013,7 @@ class YouTubeQualityTest(VideoBaseTest):
|
||||
|
||||
|
||||
@attr('a11y')
|
||||
class LMSVideoModuleA11yTest(VideoBaseTest):
|
||||
class LMSVideoBlockA11yTest(VideoBaseTest):
|
||||
"""
|
||||
LMS Video Accessibility Test Class
|
||||
"""
|
||||
@@ -1030,7 +1030,7 @@ class LMSVideoModuleA11yTest(VideoBaseTest):
|
||||
browser = 'firefox'
|
||||
|
||||
with patch.dict(os.environ, {'SELENIUM_BROWSER': browser}):
|
||||
super(LMSVideoModuleA11yTest, self).setUp()
|
||||
super(LMSVideoBlockA11yTest, self).setUp()
|
||||
|
||||
def test_video_player_a11y(self):
|
||||
# load transcripts so we can test skipping to
|
||||
|
||||
@@ -133,11 +133,11 @@ class CommandsTestBase(SharedModuleStoreTestCase):
|
||||
|
||||
video_id = text_type(test_course_key.make_usage_key('video', 'Welcome'))
|
||||
self.assertEqual(dump[video_id]['category'], 'video')
|
||||
course_metadata = dump[video_id]['metadata']
|
||||
course_metadata.pop('edx_video_id', None)
|
||||
video_metadata = dump[video_id]['metadata']
|
||||
video_metadata.pop('edx_video_id', None)
|
||||
self.assertItemsEqual(
|
||||
list(course_metadata.keys()),
|
||||
['download_video', 'youtube_id_0_75', 'youtube_id_1_0', 'youtube_id_1_25', 'youtube_id_1_5']
|
||||
list(video_metadata.keys()),
|
||||
['youtube_id_0_75', 'youtube_id_1_0', 'youtube_id_1_25', 'youtube_id_1_5']
|
||||
)
|
||||
self.assertIn('youtube_id_1_0', dump[video_id]['metadata'])
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ from xmodule.modulestore.tests.django_utils import (
|
||||
)
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, ToyCourseFactory, check_mongo_calls
|
||||
from xmodule.modulestore.tests.test_asides import AsideTestType
|
||||
from xmodule.video_module import VideoBlock
|
||||
from xmodule.x_module import STUDENT_VIEW, CombinedSystem, XModule, XModuleDescriptor
|
||||
|
||||
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
|
||||
@@ -989,14 +990,12 @@ class TestTOC(ModuleStoreTestCase):
|
||||
# - 1 for the course
|
||||
# - 1 for its children
|
||||
# - 1 for its grandchildren
|
||||
# Split makes 6 queries to load the course to depth 2:
|
||||
# - load the structure
|
||||
# - load 5 definitions
|
||||
# Split makes 5 queries to render the toc:
|
||||
# - it loads the active version at the start of the bulk operation
|
||||
# - it loads 4 definitions, because it instantiates 4 VideoModules
|
||||
# each of which access a Scope.content field in __init__
|
||||
@ddt.data((ModuleStoreEnum.Type.mongo, 3, 0, 0), (ModuleStoreEnum.Type.split, 6, 0, 5))
|
||||
# Split makes 2 queries to load the course to depth 2:
|
||||
# - 1 for the structure
|
||||
# - 1 for 5 definitions
|
||||
# Split makes 1 query to render the toc:
|
||||
# - 1 for the active version at the start of the bulk operation
|
||||
@ddt.data((ModuleStoreEnum.Type.mongo, 3, 0, 0), (ModuleStoreEnum.Type.split, 2, 0, 1))
|
||||
@ddt.unpack
|
||||
def test_toc_toy_from_chapter(self, default_ms, setup_finds, setup_sends, toc_finds):
|
||||
with self.store.default_store(default_ms):
|
||||
@@ -1031,14 +1030,12 @@ class TestTOC(ModuleStoreTestCase):
|
||||
# - 1 for the course
|
||||
# - 1 for its children
|
||||
# - 1 for its grandchildren
|
||||
# Split makes 6 queries to load the course to depth 2:
|
||||
# - load the structure
|
||||
# - load 5 definitions
|
||||
# Split makes 5 queries to render the toc:
|
||||
# - it loads the active version at the start of the bulk operation
|
||||
# - it loads 4 definitions, because it instantiates 4 VideoModules
|
||||
# each of which access a Scope.content field in __init__
|
||||
@ddt.data((ModuleStoreEnum.Type.mongo, 3, 0, 0), (ModuleStoreEnum.Type.split, 6, 0, 5))
|
||||
# Split makes 2 queries to load the course to depth 2:
|
||||
# - 1 for the structure
|
||||
# - 1 for 5 definitions
|
||||
# Split makes 1 query to render the toc:
|
||||
# - 1 for the active version at the start of the bulk operation
|
||||
@ddt.data((ModuleStoreEnum.Type.mongo, 3, 0, 0), (ModuleStoreEnum.Type.split, 2, 0, 1))
|
||||
@ddt.unpack
|
||||
def test_toc_toy_from_section(self, default_ms, setup_finds, setup_sends, toc_finds):
|
||||
with self.store.default_store(default_ms):
|
||||
@@ -1937,6 +1934,7 @@ class TestStaffDebugInfo(SharedModuleStoreTestCase):
|
||||
PER_COURSE_ANONYMIZED_DESCRIPTORS = (LTIDescriptor, )
|
||||
PER_STUDENT_ANONYMIZED_XBLOCKS = [
|
||||
ProblemBlock,
|
||||
VideoBlock,
|
||||
]
|
||||
|
||||
# The "set" here is to work around the bug that load_classes returns duplicates for multiply-declared classes.
|
||||
|
||||
@@ -25,6 +25,7 @@ from xmodule.contentstore.django import contentstore
|
||||
from xmodule.exceptions import NotFoundError
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.video_module import VideoBlock
|
||||
from xmodule.video_module.transcripts_utils import Transcript, edxval_api, subs_filename
|
||||
from xmodule.x_module import STUDENT_VIEW
|
||||
|
||||
@@ -127,7 +128,26 @@ def attach_bumper_transcript(item, filename, lang="en"):
|
||||
item.video_bumper["transcripts"][lang] = filename
|
||||
|
||||
|
||||
class TestVideo(BaseTestXmodule):
|
||||
class BaseTestVideoXBlock(BaseTestXmodule):
|
||||
"""Base class for VideoXBlock tests."""
|
||||
|
||||
CATEGORY = 'video'
|
||||
|
||||
def initialize_block(self, data=None, **kwargs):
|
||||
""" Initialize an XBlock to run tests on. """
|
||||
if data:
|
||||
# VideoBlock data field is no longer used but to avoid needing to re-do
|
||||
# a lot of tests code, parse and set the values as fields.
|
||||
fields_data = VideoBlock.parse_video_xml(data)
|
||||
kwargs.update(fields_data)
|
||||
super(BaseTestVideoXBlock, self).initialize_module(**kwargs)
|
||||
|
||||
def setUp(self):
|
||||
super(BaseTestVideoXBlock, self).setUp()
|
||||
self.initialize_block(data=self.DATA, metadata=self.METADATA)
|
||||
|
||||
|
||||
class TestVideo(BaseTestVideoXBlock):
|
||||
"""Integration tests: web client + mongo."""
|
||||
CATEGORY = "video"
|
||||
DATA = SOURCE_XML
|
||||
@@ -225,7 +245,7 @@ class TestTranscriptAvailableTranslationsDispatch(TestVideo):
|
||||
def setUp(self):
|
||||
super(TestTranscriptAvailableTranslationsDispatch, self).setUp()
|
||||
self.item_descriptor.render(STUDENT_VIEW)
|
||||
self.item = self.item_descriptor.xmodule_runtime.xmodule_instance
|
||||
self.item = self.item_descriptor
|
||||
self.subs = {"start": [10], "end": [100], "text": ["Hi, welcome to Edx."]}
|
||||
|
||||
def test_available_translation_en(self):
|
||||
@@ -383,7 +403,7 @@ class TestTranscriptAvailableTranslationsBumperDispatch(TestVideo):
|
||||
def setUp(self):
|
||||
super(TestTranscriptAvailableTranslationsBumperDispatch, self).setUp()
|
||||
self.item_descriptor.render(STUDENT_VIEW)
|
||||
self.item = self.item_descriptor.xmodule_runtime.xmodule_instance
|
||||
self.item = self.item_descriptor
|
||||
self.dispatch = "available_translations/?is_bumper=1"
|
||||
self.item.video_bumper = {"transcripts": {"en": ""}}
|
||||
|
||||
@@ -449,7 +469,7 @@ class TestTranscriptDownloadDispatch(TestVideo):
|
||||
def setUp(self):
|
||||
super(TestTranscriptDownloadDispatch, self).setUp()
|
||||
self.item_descriptor.render(STUDENT_VIEW)
|
||||
self.item = self.item_descriptor.xmodule_runtime.xmodule_instance
|
||||
self.item = self.item_descriptor
|
||||
|
||||
def test_download_transcript_not_exist(self):
|
||||
request = Request.blank('/download')
|
||||
@@ -499,7 +519,7 @@ class TestTranscriptDownloadDispatch(TestVideo):
|
||||
self.assertEqual(response.headers['Content-Disposition'], 'attachment; filename="en_塞.srt"')
|
||||
|
||||
@patch('xmodule.video_module.transcripts_utils.edxval_api.get_video_transcript_data')
|
||||
@patch('xmodule.video_module.VideoModule.get_transcript', Mock(side_effect=NotFoundError))
|
||||
@patch('xmodule.video_module.VideoBlock.get_transcript', Mock(side_effect=NotFoundError))
|
||||
def test_download_fallback_transcript(self, mock_get_video_transcript_data):
|
||||
"""
|
||||
Verify val transcript is returned as a fallback if it is not found in the content store.
|
||||
@@ -560,7 +580,7 @@ class TestTranscriptTranslationGetDispatch(TestVideo):
|
||||
def setUp(self):
|
||||
super(TestTranscriptTranslationGetDispatch, self).setUp()
|
||||
self.item_descriptor.render(STUDENT_VIEW)
|
||||
self.item = self.item_descriptor.xmodule_runtime.xmodule_instance
|
||||
self.item = self.item_descriptor
|
||||
self.item.video_bumper = {"transcripts": {"en": ""}}
|
||||
|
||||
@ddt.data(
|
||||
@@ -747,7 +767,7 @@ class TestTranscriptTranslationGetDispatch(TestVideo):
|
||||
response.headerlist
|
||||
)
|
||||
|
||||
@patch('xmodule.video_module.VideoModule.course_id', return_value='not_a_course_locator')
|
||||
@patch('xmodule.video_module.VideoBlock.course_id', return_value='not_a_course_locator')
|
||||
def test_translation_static_non_course(self, __):
|
||||
"""
|
||||
Test that get_static_transcript short-circuits in the case of a non-CourseLocator.
|
||||
@@ -769,8 +789,8 @@ class TestTranscriptTranslationGetDispatch(TestVideo):
|
||||
store.update_item(self.course, self.user.id)
|
||||
|
||||
@patch('xmodule.video_module.transcripts_utils.edxval_api.get_video_transcript_data')
|
||||
@patch('xmodule.video_module.VideoModule.translation', Mock(side_effect=NotFoundError))
|
||||
@patch('xmodule.video_module.VideoModule.get_static_transcript', Mock(return_value=Response(status=404)))
|
||||
@patch('xmodule.video_module.VideoBlock.translation', Mock(side_effect=NotFoundError))
|
||||
@patch('xmodule.video_module.VideoBlock.get_static_transcript', Mock(return_value=Response(status=404)))
|
||||
def test_translation_fallback_transcript(self, mock_get_video_transcript_data):
|
||||
"""
|
||||
Verify that the val transcript is returned as a fallback,
|
||||
@@ -801,8 +821,8 @@ class TestTranscriptTranslationGetDispatch(TestVideo):
|
||||
for attribute, value in six.iteritems(expected_headers):
|
||||
self.assertEqual(response.headers[attribute], value)
|
||||
|
||||
@patch('xmodule.video_module.VideoModule.translation', Mock(side_effect=NotFoundError))
|
||||
@patch('xmodule.video_module.VideoModule.get_static_transcript', Mock(return_value=Response(status=404)))
|
||||
@patch('xmodule.video_module.VideoBlock.translation', Mock(side_effect=NotFoundError))
|
||||
@patch('xmodule.video_module.VideoBlock.get_static_transcript', Mock(return_value=Response(status=404)))
|
||||
def test_translation_fallback_transcript_feature_disabled(self):
|
||||
"""
|
||||
Verify that val transcript is not returned when its feature is disabled.
|
||||
@@ -1132,7 +1152,7 @@ class TestGetTranscript(TestVideo):
|
||||
def setUp(self):
|
||||
super(TestGetTranscript, self).setUp()
|
||||
self.item_descriptor.render(STUDENT_VIEW)
|
||||
self.item = self.item_descriptor.xmodule_runtime.xmodule_instance
|
||||
self.item = self.item_descriptor
|
||||
|
||||
def test_good_transcript(self):
|
||||
"""
|
||||
|
||||
@@ -46,14 +46,13 @@ from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.inheritance import own_metadata
|
||||
from xmodule.modulestore.tests.django_utils import TEST_DATA_MONGO_MODULESTORE, TEST_DATA_SPLIT_MODULESTORE
|
||||
from xmodule.tests.test_import import DummySystem
|
||||
from xmodule.tests.test_video import VideoDescriptorTestBase, instantiate_descriptor
|
||||
from xmodule.video_module import VideoDescriptor, bumper_utils, video_utils
|
||||
from xmodule.tests.test_video import VideoBlockTestBase
|
||||
from xmodule.video_module import VideoBlock, bumper_utils, video_utils
|
||||
from xmodule.video_module.transcripts_utils import Transcript, save_to_store, subs_filename
|
||||
from xmodule.video_module.video_module import EXPORT_IMPORT_COURSE_DIR, EXPORT_IMPORT_STATIC_DIR
|
||||
from xmodule.x_module import PUBLIC_VIEW, STUDENT_VIEW
|
||||
|
||||
from .helpers import BaseTestXmodule
|
||||
from .test_video_handlers import TestVideo
|
||||
from .test_video_handlers import BaseTestVideoXBlock, TestVideo
|
||||
from .test_video_xml import SOURCE_XML
|
||||
|
||||
MODULESTORES = {
|
||||
@@ -96,7 +95,7 @@ class TestVideoYouTube(TestVideo):
|
||||
'metadata': json.dumps(OrderedDict({
|
||||
'autoAdvance': False,
|
||||
'saveStateEnabled': True,
|
||||
'saveStateUrl': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'saveStateUrl': self.item_descriptor.ajax_url + '/save_user_state',
|
||||
'autoplay': False,
|
||||
'streams': '0.75:jNCf2gIqpeE,1.00:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg',
|
||||
'sources': sources,
|
||||
@@ -146,7 +145,7 @@ class TestVideoNonYouTube(TestVideo):
|
||||
display_name="A Name"
|
||||
sub="a_sub_file.srt.sjson"
|
||||
download_video="true"
|
||||
start_time="01:00:03" end_time="01:00:10"
|
||||
start_time="3603.0" end_time="3610.0"
|
||||
>
|
||||
<source src="example.mp4"/>
|
||||
<source src="example.webm"/>
|
||||
@@ -178,7 +177,7 @@ class TestVideoNonYouTube(TestVideo):
|
||||
'metadata': json.dumps(OrderedDict({
|
||||
'autoAdvance': False,
|
||||
'saveStateEnabled': True,
|
||||
'saveStateUrl': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'saveStateUrl': self.item_descriptor.ajax_url + '/save_user_state',
|
||||
'autoplay': False,
|
||||
'streams': '1.00:3_yD_cEKoCk',
|
||||
'sources': sources,
|
||||
@@ -222,7 +221,7 @@ class TestVideoNonYouTube(TestVideo):
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestGetHtmlMethod(BaseTestXmodule):
|
||||
class TestGetHtmlMethod(BaseTestVideoXBlock):
|
||||
'''
|
||||
Make sure that `get_html` works correctly.
|
||||
'''
|
||||
@@ -280,7 +279,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
<video show_captions="true"
|
||||
display_name="A Name"
|
||||
sub="{sub}" download_track="{download_track}"
|
||||
start_time="01:00:03" end_time="01:00:10" download_video="true"
|
||||
start_time="3603.0" end_time="3610.0" download_video="true"
|
||||
>
|
||||
<source src="example.mp4"/>
|
||||
<source src="example.webm"/>
|
||||
@@ -360,7 +359,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
transcripts=data['transcripts'],
|
||||
)
|
||||
|
||||
self.initialize_module(data=DATA)
|
||||
self.initialize_block(data=DATA)
|
||||
track_url = self.get_handler_url('transcript', 'download')
|
||||
|
||||
context = self.item_descriptor.render(STUDENT_VIEW).content
|
||||
@@ -370,7 +369,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
'transcriptTranslationUrl': self.get_handler_url('transcript', 'translation/__lang__'),
|
||||
'transcriptAvailableTranslationsUrl': self.get_handler_url('transcript', 'available_translations'),
|
||||
'publishCompletionUrl': self.get_handler_url('publish_completion', ''),
|
||||
'saveStateUrl': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'saveStateUrl': self.item_descriptor.ajax_url + '/save_user_state',
|
||||
})
|
||||
expected_context.update({
|
||||
'transcript_download_format': (
|
||||
@@ -394,7 +393,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
display_name="A Name"
|
||||
sub="a_sub_file.srt.sjson" source="{source}"
|
||||
download_video="{download_video}"
|
||||
start_time="01:00:03" end_time="01:00:10"
|
||||
start_time="3603.0" end_time="3610.0"
|
||||
>
|
||||
{sources}
|
||||
</video>
|
||||
@@ -409,7 +408,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
<source src="example.webm"/>
|
||||
""",
|
||||
'result': {
|
||||
'download_video_link': u'example_source.mp4',
|
||||
'download_video_link': u'example.mp4',
|
||||
'sources': [u'example.mp4', u'example.webm'],
|
||||
},
|
||||
},
|
||||
@@ -474,7 +473,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
source=data['source'],
|
||||
sources=data['sources']
|
||||
)
|
||||
self.initialize_module(data=DATA)
|
||||
self.initialize_block(data=DATA)
|
||||
context = self.item_descriptor.render(STUDENT_VIEW).content
|
||||
|
||||
expected_context = dict(initial_context)
|
||||
@@ -482,7 +481,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
'transcriptTranslationUrl': self.get_handler_url('transcript', 'translation/__lang__'),
|
||||
'transcriptAvailableTranslationsUrl': self.get_handler_url('transcript', 'available_translations'),
|
||||
'publishCompletionUrl': self.get_handler_url('publish_completion', ''),
|
||||
'saveStateUrl': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'saveStateUrl': self.item_descriptor.ajax_url + '/save_user_state',
|
||||
'sources': data['result'].get('sources', []),
|
||||
})
|
||||
expected_context.update({
|
||||
@@ -498,14 +497,14 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
|
||||
def test_get_html_with_non_existent_edx_video_id(self):
|
||||
"""
|
||||
Tests the VideoModule get_html where a edx_video_id is given but a video is not found
|
||||
Tests the VideoBlock get_html where a edx_video_id is given but a video is not found
|
||||
"""
|
||||
SOURCE_XML = u"""
|
||||
<video show_captions="true"
|
||||
display_name="A Name"
|
||||
sub="a_sub_file.srt.sjson" source="{source}"
|
||||
download_video="{download_video}"
|
||||
start_time="01:00:03" end_time="01:00:10"
|
||||
start_time="3603.0" end_time="3610.0"
|
||||
edx_video_id="{edx_video_id}"
|
||||
>
|
||||
{sources}
|
||||
@@ -520,7 +519,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
""",
|
||||
'edx_video_id': "meow",
|
||||
'result': {
|
||||
'download_video_link': u'example_source.mp4',
|
||||
'download_video_link': u'example.mp4',
|
||||
'sources': [u'example.mp4', u'example.webm'],
|
||||
}
|
||||
}
|
||||
@@ -530,11 +529,11 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
sources=no_video_data['sources'],
|
||||
edx_video_id=no_video_data['edx_video_id']
|
||||
)
|
||||
self.initialize_module(data=DATA)
|
||||
self.initialize_block(data=DATA)
|
||||
|
||||
# Referencing a non-existent VAL ID in courseware won't cause an error --
|
||||
# it'll just fall back to the values in the VideoDescriptor.
|
||||
self.assertIn("example_source.mp4", self.item_descriptor.render(STUDENT_VIEW).content)
|
||||
# it'll just fall back to the values in the VideoBlock.
|
||||
self.assertIn("example.mp4", self.item_descriptor.render(STUDENT_VIEW).content)
|
||||
|
||||
def test_get_html_with_mocked_edx_video_id(self):
|
||||
SOURCE_XML = """
|
||||
@@ -542,7 +541,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
display_name="A Name"
|
||||
sub="a_sub_file.srt.sjson" source="{source}"
|
||||
download_video="{download_video}"
|
||||
start_time="01:00:03" end_time="01:00:10"
|
||||
start_time="3603.0" end_time="3610.0"
|
||||
edx_video_id="{edx_video_id}"
|
||||
>
|
||||
{sources}
|
||||
@@ -596,7 +595,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
sources=data['sources'],
|
||||
edx_video_id=data['edx_video_id']
|
||||
)
|
||||
self.initialize_module(data=DATA)
|
||||
self.initialize_block(data=DATA)
|
||||
|
||||
with patch('edxval.api.get_video_info') as mock_get_video_info:
|
||||
mock_get_video_info.return_value = {
|
||||
@@ -620,7 +619,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
'transcriptTranslationUrl': self.get_handler_url('transcript', 'translation/__lang__'),
|
||||
'transcriptAvailableTranslationsUrl': self.get_handler_url('transcript', 'available_translations'),
|
||||
'publishCompletionUrl': self.get_handler_url('publish_completion', ''),
|
||||
'saveStateUrl': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'saveStateUrl': self.item_descriptor.ajax_url + '/save_user_state',
|
||||
'sources': data['result']['sources'],
|
||||
})
|
||||
expected_context.update({
|
||||
@@ -636,7 +635,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
|
||||
def test_get_html_with_existing_edx_video_id(self):
|
||||
"""
|
||||
Tests the `VideoModule` `get_html` where `edx_video_id` is given and related video is found
|
||||
Tests the `VideoBlock` `get_html` where `edx_video_id` is given and related video is found
|
||||
"""
|
||||
edx_video_id = 'thundercats'
|
||||
# create video with provided edx_video_id and return encoded_videos
|
||||
@@ -665,7 +664,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
|
||||
def test_get_html_with_existing_unstripped_edx_video_id(self):
|
||||
"""
|
||||
Tests the `VideoModule` `get_html` where `edx_video_id` with some unwanted tab(\t)
|
||||
Tests the `VideoBlock` `get_html` where `edx_video_id` with some unwanted tab(\t)
|
||||
is given and related video is found
|
||||
"""
|
||||
edx_video_id = 'thundercats'
|
||||
@@ -731,7 +730,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
display_name="A Name"
|
||||
sub="a_sub_file.srt.sjson" source="{source}"
|
||||
download_video="{download_video}"
|
||||
start_time="01:00:03" end_time="01:00:10"
|
||||
start_time="3603.0" end_time="3610.0"
|
||||
edx_video_id="{edx_video_id}"
|
||||
>
|
||||
{sources}
|
||||
@@ -769,7 +768,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
sources=data['sources'],
|
||||
edx_video_id=data['edx_video_id']
|
||||
)
|
||||
self.initialize_module(data=DATA)
|
||||
self.initialize_block(data=DATA)
|
||||
# context returned by get_html
|
||||
context = self.item_descriptor.render(STUDENT_VIEW).content
|
||||
|
||||
@@ -779,7 +778,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
'transcriptTranslationUrl': self.get_handler_url('transcript', 'translation/__lang__'),
|
||||
'transcriptAvailableTranslationsUrl': self.get_handler_url('transcript', 'available_translations'),
|
||||
'publishCompletionUrl': self.get_handler_url('publish_completion', ''),
|
||||
'saveStateUrl': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'saveStateUrl': self.item_descriptor.ajax_url + '/save_user_state',
|
||||
'sources': data['result']['sources'],
|
||||
})
|
||||
expected_context.update({
|
||||
@@ -820,7 +819,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
sub="a_sub_file.srt.sjson" source="{source}"
|
||||
download_video="{download_video}"
|
||||
edx_video_id="{edx_video_id}"
|
||||
start_time="01:00:03" end_time="01:00:10"
|
||||
start_time="3603.0" end_time="3610.0"
|
||||
>
|
||||
{sources}
|
||||
</video>
|
||||
@@ -834,7 +833,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
<source src="http://example.com/example.webm"/>
|
||||
""",
|
||||
'result': {
|
||||
'download_video_link': u'example_source.mp4',
|
||||
'download_video_link': u'http://example.com/example.mp4',
|
||||
'sources': [
|
||||
u'http://cdn-example.com/example.mp4',
|
||||
u'http://cdn-example.com/example.webm'
|
||||
@@ -881,7 +880,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
sources=data['sources'],
|
||||
edx_video_id=data['edx_video_id'],
|
||||
)
|
||||
self.initialize_module(data=DATA)
|
||||
self.initialize_block(data=DATA)
|
||||
self.item_descriptor.xmodule_runtime.user_location = 'CN'
|
||||
context = self.item_descriptor.render('student_view').content
|
||||
expected_context = dict(initial_context)
|
||||
@@ -889,7 +888,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
'transcriptTranslationUrl': self.get_handler_url('transcript', 'translation/__lang__'),
|
||||
'transcriptAvailableTranslationsUrl': self.get_handler_url('transcript', 'available_translations'),
|
||||
'publishCompletionUrl': self.get_handler_url('publish_completion', ''),
|
||||
'saveStateUrl': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'saveStateUrl': self.item_descriptor.ajax_url + '/save_user_state',
|
||||
'sources': data['result'].get('sources', []),
|
||||
})
|
||||
expected_context.update({
|
||||
@@ -919,7 +918,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
sub="a_sub_file.srt.sjson" source="{source}"
|
||||
download_video="{download_video}"
|
||||
edx_video_id="{edx_video_id}"
|
||||
start_time="01:00:03" end_time="01:00:10"
|
||||
start_time="3603.0" end_time="3610.0"
|
||||
>
|
||||
{sources}
|
||||
</video>
|
||||
@@ -932,7 +931,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
<source src="http://example.com/example.mp4"/>
|
||||
""",
|
||||
'result': {
|
||||
'download_video_link': u'example_source.mp4',
|
||||
'download_video_link': u'http://example.com/example.mp4',
|
||||
'sources': [
|
||||
u'http://example.com/example.mp4',
|
||||
],
|
||||
@@ -972,7 +971,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
sources=data['sources'],
|
||||
edx_video_id=data['edx_video_id'],
|
||||
)
|
||||
self.initialize_module(data=DATA)
|
||||
self.initialize_block(data=DATA)
|
||||
|
||||
# Mocking the edxval API call because if not done,
|
||||
# the method throws exception as no VAL entry is found
|
||||
@@ -992,7 +991,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
'transcriptTranslationUrl': self.get_handler_url('transcript', 'translation/__lang__'),
|
||||
'transcriptAvailableTranslationsUrl': self.get_handler_url('transcript', 'available_translations'),
|
||||
'publishCompletionUrl': self.get_handler_url('publish_completion', ''),
|
||||
'saveStateUrl': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'saveStateUrl': self.item_descriptor.ajax_url + '/save_user_state',
|
||||
'sources': data['result'].get('sources', []),
|
||||
})
|
||||
expected_context.update({
|
||||
@@ -1025,7 +1024,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
with patch('xmodule.video_module.video_module.HLSPlaybackEnabledFlag.feature_enabled') as feature_enabled:
|
||||
feature_enabled.return_value = hls_feature_enabled
|
||||
video_xml = '<video display_name="Video" download_video="true" edx_video_id="12345-67890">[]</video>'
|
||||
self.initialize_module(data=video_xml)
|
||||
self.initialize_block(data=video_xml)
|
||||
self.item_descriptor.render(STUDENT_VIEW)
|
||||
get_urls_for_profiles.assert_called_with(
|
||||
self.item_descriptor.edx_video_id,
|
||||
@@ -1050,7 +1049,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
'desktop_mp4': 'https://mp4.com/dm.mp4'
|
||||
}
|
||||
|
||||
self.initialize_module(data=video_xml)
|
||||
self.initialize_block(data=video_xml)
|
||||
context = self.item_descriptor.render(STUDENT_VIEW).content
|
||||
|
||||
self.assertIn("'download_video_link': 'https://mp4.com/dm.mp4'", context)
|
||||
@@ -1069,7 +1068,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
</video>
|
||||
"""
|
||||
|
||||
self.initialize_module(data=video_xml)
|
||||
self.initialize_block(data=video_xml)
|
||||
context = self.item_descriptor.render(STUDENT_VIEW).content
|
||||
self.assertIn("'download_video_link': None", context)
|
||||
|
||||
@@ -1083,7 +1082,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
</video>
|
||||
"""
|
||||
|
||||
self.initialize_module(data=video_xml)
|
||||
self.initialize_block(data=video_xml)
|
||||
context = self.item_descriptor.render(STUDENT_VIEW).content
|
||||
self.assertIn('"saveStateEnabled": true', context)
|
||||
context = self.item_descriptor.render(PUBLIC_VIEW).content
|
||||
@@ -1097,7 +1096,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
video_xml = '<video display_name="Video" download_video="true" edx_video_id="12345-67890">[]</video>'
|
||||
get_course_video_image_url.return_value = '/media/video-images/poster.png'
|
||||
|
||||
self.initialize_module(data=video_xml)
|
||||
self.initialize_block(data=video_xml)
|
||||
context = self.item_descriptor.render(STUDENT_VIEW).content
|
||||
|
||||
self.assertIn('"poster": "/media/video-images/poster.png"', context)
|
||||
@@ -1110,7 +1109,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
video_xml = '<video display_name="Video" download_video="true" edx_video_id="null">[]</video>'
|
||||
get_course_video_image_url.return_value = '/media/video-images/poster.png'
|
||||
|
||||
self.initialize_module(data=video_xml)
|
||||
self.initialize_block(data=video_xml)
|
||||
context = self.item_descriptor.render(STUDENT_VIEW).content
|
||||
|
||||
self.assertIn("\'poster\': \'null\'", context)
|
||||
@@ -1121,7 +1120,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
Verify that `prioritize_hls` is set to `False` if `HLSPlaybackEnabledFlag` is disabled.
|
||||
"""
|
||||
video_xml = '<video display_name="Video" download_video="true" edx_video_id="12345-67890">[]</video>'
|
||||
self.initialize_module(data=video_xml)
|
||||
self.initialize_block(data=video_xml)
|
||||
context = self.item_descriptor.render(STUDENT_VIEW).content
|
||||
self.assertIn('"prioritizeHls": false', context)
|
||||
|
||||
@@ -1176,13 +1175,13 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
DEPRECATE_YOUTUBE_FLAG = waffle_flags()[DEPRECATE_YOUTUBE]
|
||||
with patch.object(WaffleFlagCourseOverrideModel, 'override_value', return_value=data['course_override']):
|
||||
with override_flag(DEPRECATE_YOUTUBE_FLAG.namespaced_flag_name, active=data['waffle_enabled']):
|
||||
self.initialize_module(data=video_xml, metadata=metadata)
|
||||
self.initialize_block(data=video_xml, metadata=metadata)
|
||||
context = self.item_descriptor.render(STUDENT_VIEW).content
|
||||
self.assertIn(u'"prioritizeHls": {}'.format(data['result']), context)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestVideoDescriptorInitialization(BaseTestXmodule):
|
||||
class TestVideoBlockInitialization(BaseTestVideoXBlock):
|
||||
"""
|
||||
Make sure that module initialization works correctly.
|
||||
"""
|
||||
@@ -1191,66 +1190,9 @@ class TestVideoDescriptorInitialization(BaseTestXmodule):
|
||||
METADATA = {}
|
||||
|
||||
def setUp(self):
|
||||
super(TestVideoDescriptorInitialization, self).setUp()
|
||||
super(TestVideoBlockInitialization, self).setUp()
|
||||
self.setup_course()
|
||||
|
||||
def test_source_not_in_html5sources(self):
|
||||
metadata = {
|
||||
'source': 'http://example.org/video.mp4',
|
||||
'html5_sources': ['http://youtu.be/3_yD_cEKoCk.mp4'],
|
||||
}
|
||||
|
||||
self.initialize_module(metadata=metadata)
|
||||
fields = self.item_descriptor.editable_metadata_fields
|
||||
|
||||
self.assertIn('source', fields)
|
||||
self.assertEqual(self.item_descriptor.source, 'http://example.org/video.mp4')
|
||||
self.assertTrue(self.item_descriptor.download_video)
|
||||
self.assertTrue(self.item_descriptor.source_visible)
|
||||
|
||||
def test_source_in_html5sources(self):
|
||||
metadata = {
|
||||
'source': 'http://example.org/video.mp4',
|
||||
'html5_sources': ['http://example.org/video.mp4'],
|
||||
}
|
||||
|
||||
self.initialize_module(metadata=metadata)
|
||||
fields = self.item_descriptor.editable_metadata_fields
|
||||
|
||||
self.assertNotIn('source', fields)
|
||||
self.assertTrue(self.item_descriptor.download_video)
|
||||
self.assertFalse(self.item_descriptor.source_visible)
|
||||
|
||||
def test_download_video_is_explicitly_set(self):
|
||||
metadata = {
|
||||
'track': u'http://some_track.srt',
|
||||
'source': 'http://example.org/video.mp4',
|
||||
'html5_sources': ['http://youtu.be/3_yD_cEKoCk.mp4'],
|
||||
'download_video': False,
|
||||
}
|
||||
|
||||
self.initialize_module(metadata=metadata)
|
||||
|
||||
fields = self.item_descriptor.editable_metadata_fields
|
||||
self.assertIn('source', fields)
|
||||
self.assertIn('download_video', fields)
|
||||
|
||||
self.assertFalse(self.item_descriptor.download_video)
|
||||
self.assertTrue(self.item_descriptor.source_visible)
|
||||
self.assertTrue(self.item_descriptor.download_track)
|
||||
|
||||
def test_source_is_empty(self):
|
||||
metadata = {
|
||||
'source': '',
|
||||
'html5_sources': ['http://youtu.be/3_yD_cEKoCk.mp4'],
|
||||
}
|
||||
|
||||
self.initialize_module(metadata=metadata)
|
||||
fields = self.item_descriptor.editable_metadata_fields
|
||||
|
||||
self.assertNotIn('source', fields)
|
||||
self.assertFalse(self.item_descriptor.download_video)
|
||||
|
||||
@ddt.data(
|
||||
(
|
||||
{
|
||||
@@ -1295,7 +1237,7 @@ class TestVideoDescriptorInitialization(BaseTestXmodule):
|
||||
"""
|
||||
with patch('xmodule.video_module.video_module.edxval_api.get_urls_for_profiles') as get_urls_for_profiles:
|
||||
get_urls_for_profiles.return_value = val_video_encodings
|
||||
self.initialize_module(
|
||||
self.initialize_block(
|
||||
data='<video display_name="Video" download_video="true" edx_video_id="12345-67890">[]</video>'
|
||||
)
|
||||
context = self.item_descriptor.get_context()
|
||||
@@ -1332,7 +1274,7 @@ class TestVideoDescriptorInitialization(BaseTestXmodule):
|
||||
"""
|
||||
with patch('xmodule.video_module.video_module.edxval_api.get_urls_for_profiles') as get_urls_for_profiles:
|
||||
get_urls_for_profiles.return_value = val_video_encodings
|
||||
self.initialize_module(
|
||||
self.initialize_block(
|
||||
data='<video display_name="Video" youtube_id_1_0="" download_video="true" edx_video_id="12345-67890">[]</video>'
|
||||
)
|
||||
context = self.item_descriptor.get_context()
|
||||
@@ -1340,7 +1282,7 @@ class TestVideoDescriptorInitialization(BaseTestXmodule):
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestEditorSavedMethod(BaseTestXmodule):
|
||||
class TestEditorSavedMethod(BaseTestVideoXBlock):
|
||||
"""
|
||||
Make sure that `editor_saved` method works correctly.
|
||||
"""
|
||||
@@ -1369,7 +1311,7 @@ class TestEditorSavedMethod(BaseTestXmodule):
|
||||
for video.
|
||||
"""
|
||||
self.MODULESTORE = MODULESTORES[default_store] # pylint: disable=invalid-name
|
||||
self.initialize_module(metadata=self.metadata)
|
||||
self.initialize_block(metadata=self.metadata)
|
||||
item = self.store.get_item(self.item_descriptor.location)
|
||||
with open(self.file_path, "r") as myfile:
|
||||
save_to_store(myfile.read(), self.file_name, 'text/sjson', item.location)
|
||||
@@ -1389,7 +1331,7 @@ class TestEditorSavedMethod(BaseTestXmodule):
|
||||
sub will be generated by editor_saved function.
|
||||
"""
|
||||
self.MODULESTORE = MODULESTORES[default_store]
|
||||
self.initialize_module(metadata=self.metadata)
|
||||
self.initialize_block(metadata=self.metadata)
|
||||
item = self.store.get_item(self.item_descriptor.location)
|
||||
with open(self.file_path, "r") as myfile:
|
||||
save_to_store(myfile.read(), self.file_name, 'text/sjson', item.location)
|
||||
@@ -1414,7 +1356,7 @@ class TestEditorSavedMethod(BaseTestXmodule):
|
||||
self.metadata.update({
|
||||
'edx_video_id': unstripped_video_id
|
||||
})
|
||||
self.initialize_module(metadata=self.metadata)
|
||||
self.initialize_block(metadata=self.metadata)
|
||||
item = self.store.get_item(self.item_descriptor.location)
|
||||
self.assertEqual(item.edx_video_id, unstripped_video_id)
|
||||
|
||||
@@ -1432,7 +1374,7 @@ class TestEditorSavedMethod(BaseTestXmodule):
|
||||
for a given `edx_video_id`.
|
||||
"""
|
||||
self.MODULESTORE = MODULESTORES[default_store]
|
||||
self.initialize_module(metadata=self.metadata)
|
||||
self.initialize_block(metadata=self.metadata)
|
||||
item = self.store.get_item(self.item_descriptor.location)
|
||||
self.assertEqual(item.youtube_id_1_0, '3_yD_cEKoCk')
|
||||
|
||||
@@ -1444,14 +1386,14 @@ class TestEditorSavedMethod(BaseTestXmodule):
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestVideoDescriptorStudentViewJson(CacheIsolationTestCase):
|
||||
class TestVideoBlockStudentViewJson(BaseTestVideoXBlock, CacheIsolationTestCase):
|
||||
"""
|
||||
Tests for the student_view_data method on VideoDescriptor.
|
||||
Tests for the student_view_data method on VideoBlock.
|
||||
"""
|
||||
TEST_DURATION = 111.0
|
||||
TEST_PROFILE = "mobile"
|
||||
TEST_SOURCE_URL = "http://www.example.com/source.mp4"
|
||||
TEST_LANGUAGE = "ge"
|
||||
TEST_SOURCE_URL = u"http://www.example.com/source.mp4"
|
||||
TEST_LANGUAGE = u"ge"
|
||||
TEST_ENCODED_VIDEO = {
|
||||
'profile': TEST_PROFILE,
|
||||
'bitrate': 333,
|
||||
@@ -1460,10 +1402,10 @@ class TestVideoDescriptorStudentViewJson(CacheIsolationTestCase):
|
||||
}
|
||||
TEST_EDX_VIDEO_ID = 'test_edx_video_id'
|
||||
TEST_YOUTUBE_ID = 'test_youtube_id'
|
||||
TEST_YOUTUBE_EXPECTED_URL = 'https://www.youtube.com/watch?v=test_youtube_id'
|
||||
TEST_YOUTUBE_EXPECTED_URL = u'https://www.youtube.com/watch?v=test_youtube_id'
|
||||
|
||||
def setUp(self):
|
||||
super(TestVideoDescriptorStudentViewJson, self).setUp()
|
||||
super(TestVideoBlockStudentViewJson, self).setUp()
|
||||
video_declaration = (
|
||||
"<video display_name='Test Video' edx_video_id='123' youtube_id_1_0=\'" + self.TEST_YOUTUBE_ID + "\'>"
|
||||
)
|
||||
@@ -1474,7 +1416,8 @@ class TestVideoDescriptorStudentViewJson(CacheIsolationTestCase):
|
||||
"</video>"]
|
||||
)
|
||||
self.transcript_url = "transcript_url"
|
||||
self.video = instantiate_descriptor(data=sample_xml)
|
||||
self.initialize_block(data=sample_xml)
|
||||
self.video = self.item_descriptor
|
||||
self.video.runtime.handler_url = Mock(return_value=self.transcript_url)
|
||||
self.video.runtime.course_id = MagicMock()
|
||||
|
||||
@@ -1576,7 +1519,8 @@ class TestVideoDescriptorStudentViewJson(CacheIsolationTestCase):
|
||||
"</video>"
|
||||
])
|
||||
self.transcript_url = "transcript_url"
|
||||
self.video = instantiate_descriptor(data=sample_xml)
|
||||
self.initialize_block(data=sample_xml)
|
||||
self.video = self.item_descriptor
|
||||
self.video.runtime.handler_url = Mock(return_value=self.transcript_url)
|
||||
self.video.runtime.course_id = MagicMock()
|
||||
result = self.get_result()
|
||||
@@ -1639,12 +1583,12 @@ class TestVideoDescriptorStudentViewJson(CacheIsolationTestCase):
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class VideoDescriptorTest(TestCase, VideoDescriptorTestBase):
|
||||
class VideoBlockTest(TestCase, VideoBlockTestBase):
|
||||
"""
|
||||
Tests for video descriptor that requires access to django settings.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(VideoDescriptorTest, self).setUp()
|
||||
super(VideoBlockTest, self).setUp()
|
||||
self.descriptor.runtime.handler_url = MagicMock()
|
||||
self.descriptor.runtime.course_id = MagicMock()
|
||||
self.temp_dir = mkdtemp()
|
||||
@@ -1724,7 +1668,7 @@ class VideoDescriptorTest(TestCase, VideoDescriptorTestBase):
|
||||
|
||||
actual = self.descriptor.definition_to_xml(resource_fs=self.file_system)
|
||||
expected_str = u"""
|
||||
<video download_video="false" url_name="SampleProblem" transcripts='{transcripts}'>
|
||||
<video url_name="SampleProblem" transcripts='{transcripts}'>
|
||||
<video_asset client_video_id="test_client_video_id" duration="111.0" image="">
|
||||
<encoded_video profile="mobile" url="http://example.com/video" file_size="222" bitrate="333"/>
|
||||
<transcripts>
|
||||
@@ -1816,7 +1760,7 @@ class VideoDescriptorTest(TestCase, VideoDescriptorTestBase):
|
||||
"""
|
||||
self.descriptor.edx_video_id = 'nonexistent'
|
||||
actual = self.descriptor.definition_to_xml(resource_fs=self.file_system)
|
||||
expected_str = """<video download_video="false" url_name="SampleProblem"/>"""
|
||||
expected_str = """<video url_name="SampleProblem"/>"""
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
expected = etree.XML(expected_str, parser=parser)
|
||||
self.assertXmlEqual(expected, actual)
|
||||
@@ -1829,7 +1773,7 @@ class VideoDescriptorTest(TestCase, VideoDescriptorTestBase):
|
||||
mock_get_video_ids_info.return_value = True, []
|
||||
|
||||
actual = self.descriptor.definition_to_xml(resource_fs=self.file_system)
|
||||
expected_str = '<video url_name="SampleProblem" download_video="false"></video>'
|
||||
expected_str = '<video url_name="SampleProblem"></video>'
|
||||
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
expected = etree.XML(expected_str, parser=parser)
|
||||
@@ -2162,7 +2106,7 @@ class VideoDescriptorTest(TestCase, VideoDescriptorTestBase):
|
||||
</video>
|
||||
"""
|
||||
with self.assertRaises(ValCannotCreateError):
|
||||
VideoDescriptor.from_xml(xml_data, module_system, id_generator=Mock())
|
||||
VideoBlock.from_xml(xml_data, module_system, id_generator=Mock())
|
||||
with self.assertRaises(ValVideoNotFoundError):
|
||||
get_video_info("test_edx_video_id")
|
||||
|
||||
@@ -2226,7 +2170,7 @@ class TestVideoWithBumper(TestVideo):
|
||||
'branding_info': None,
|
||||
'license': None,
|
||||
'bumper_metadata': json.dumps(OrderedDict({
|
||||
'saveStateUrl': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'saveStateUrl': self.item_descriptor.ajax_url + '/save_user_state',
|
||||
'showCaptions': 'true',
|
||||
'sources': ['http://test_bumper.mp4'],
|
||||
'streams': '',
|
||||
@@ -2251,7 +2195,7 @@ class TestVideoWithBumper(TestVideo):
|
||||
'metadata': json.dumps(OrderedDict({
|
||||
'autoAdvance': False,
|
||||
'saveStateEnabled': True,
|
||||
'saveStateUrl': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'saveStateUrl': self.item_descriptor.ajax_url + '/save_user_state',
|
||||
'autoplay': False,
|
||||
'streams': '0.75:jNCf2gIqpeE,1.00:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg',
|
||||
'sources': sources,
|
||||
@@ -2325,7 +2269,7 @@ class TestAutoAdvanceVideo(TestVideo):
|
||||
'metadata': json.dumps(OrderedDict({
|
||||
'autoAdvance': autoadvance_flag,
|
||||
'saveStateEnabled': True,
|
||||
'saveStateUrl': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'saveStateUrl': self.item_descriptor.ajax_url + '/save_user_state',
|
||||
'autoplay': False,
|
||||
'streams': '0.75:jNCf2gIqpeE,1.00:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg',
|
||||
'sources': [u'example.mp4', u'example.webm'],
|
||||
@@ -2395,8 +2339,8 @@ class TestAutoAdvanceVideo(TestVideo):
|
||||
"""
|
||||
# This first render is done to initialize the instance
|
||||
self.item_descriptor.render(STUDENT_VIEW)
|
||||
item_instance = self.item_descriptor.xmodule_runtime.xmodule_instance
|
||||
item_instance.video_auto_advance = new_value
|
||||
self.item_descriptor.video_auto_advance = new_value
|
||||
self.item_descriptor._reset_dirty_field(self.item_descriptor.fields['video_auto_advance']) # pylint: disable=protected-access
|
||||
# After this step, render() should see the new value
|
||||
# e.g. use self.item_descriptor.render(STUDENT_VIEW).content
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ course, section, subsection, unit, etc.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from xmodule.tests import LogicTest
|
||||
from xmodule.video_module import VideoDescriptor
|
||||
from django.test import TestCase
|
||||
from xmodule.video_module import VideoBlock
|
||||
|
||||
SOURCE_XML = """
|
||||
<video show_captions="true"
|
||||
@@ -25,7 +25,7 @@ SOURCE_XML = """
|
||||
youtube="0.75:jNCf2gIqpeE,1.0:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg"
|
||||
sub="a_sub_file.srt.sjson"
|
||||
download_video="true"
|
||||
start_time="01:00:03" end_time="01:00:10"
|
||||
start_time="3603.0" end_time="3610.0"
|
||||
>
|
||||
<source src="example.mp4"/>
|
||||
<source src="example.webm"/>
|
||||
@@ -34,10 +34,8 @@ SOURCE_XML = """
|
||||
"""
|
||||
|
||||
|
||||
class VideoModuleLogicTest(LogicTest):
|
||||
"""Tests for logic of Video Xmodule."""
|
||||
|
||||
descriptor_class = VideoDescriptor
|
||||
class VideoBlockLogicTest(TestCase):
|
||||
"""Tests for logic of VideoBlock."""
|
||||
|
||||
raw_field_data = {
|
||||
'data': '<video />'
|
||||
@@ -46,7 +44,7 @@ class VideoModuleLogicTest(LogicTest):
|
||||
def test_parse_youtube(self):
|
||||
"""Test parsing old-style Youtube ID strings into a dict."""
|
||||
youtube_str = '0.75:jNCf2gIqpeE,1.00:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg'
|
||||
output = VideoDescriptor._parse_youtube(youtube_str)
|
||||
output = VideoBlock._parse_youtube(youtube_str)
|
||||
self.assertEqual(output, {'0.75': 'jNCf2gIqpeE',
|
||||
'1.00': 'ZwkTiUPN0mg',
|
||||
'1.25': 'rsq9auxASqI',
|
||||
@@ -58,7 +56,7 @@ class VideoModuleLogicTest(LogicTest):
|
||||
empty string.
|
||||
"""
|
||||
youtube_str = '0.75:jNCf2gIqpeE'
|
||||
output = VideoDescriptor._parse_youtube(youtube_str)
|
||||
output = VideoBlock._parse_youtube(youtube_str)
|
||||
self.assertEqual(output, {'0.75': 'jNCf2gIqpeE',
|
||||
'1.00': '',
|
||||
'1.25': '',
|
||||
@@ -71,8 +69,8 @@ class VideoModuleLogicTest(LogicTest):
|
||||
youtube_str = '1.00:p2Q6BrNhdh8'
|
||||
youtube_str_hack = '1.0:p2Q6BrNhdh8'
|
||||
self.assertEqual(
|
||||
VideoDescriptor._parse_youtube(youtube_str),
|
||||
VideoDescriptor._parse_youtube(youtube_str_hack)
|
||||
VideoBlock._parse_youtube(youtube_str),
|
||||
VideoBlock._parse_youtube(youtube_str_hack)
|
||||
)
|
||||
|
||||
def test_parse_youtube_empty(self):
|
||||
@@ -80,7 +78,7 @@ class VideoModuleLogicTest(LogicTest):
|
||||
Some courses have empty youtube attributes, so we should handle
|
||||
that well.
|
||||
"""
|
||||
self.assertEqual(VideoDescriptor._parse_youtube(''),
|
||||
self.assertEqual(VideoBlock._parse_youtube(''),
|
||||
{'0.75': '',
|
||||
'1.00': '',
|
||||
'1.25': '',
|
||||
|
||||
@@ -806,8 +806,8 @@ FACEBOOK_APP_SECRET = AUTH_TOKENS.get("FACEBOOK_APP_SECRET")
|
||||
FACEBOOK_APP_ID = AUTH_TOKENS.get("FACEBOOK_APP_ID")
|
||||
|
||||
XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {})
|
||||
XBLOCK_SETTINGS.setdefault("VideoDescriptor", {})["licensing_enabled"] = FEATURES.get("LICENSING", False)
|
||||
XBLOCK_SETTINGS.setdefault("VideoModule", {})['YOUTUBE_API_KEY'] = AUTH_TOKENS.get('YOUTUBE_API_KEY', YOUTUBE_API_KEY)
|
||||
XBLOCK_SETTINGS.setdefault("VideoBlock", {})["licensing_enabled"] = FEATURES.get("LICENSING", False)
|
||||
XBLOCK_SETTINGS.setdefault("VideoBlock", {})['YOUTUBE_API_KEY'] = AUTH_TOKENS.get('YOUTUBE_API_KEY', YOUTUBE_API_KEY)
|
||||
|
||||
##### VIDEO IMAGE STORAGE #####
|
||||
VIDEO_IMAGE_SETTINGS = ENV_TOKENS.get('VIDEO_IMAGE_SETTINGS', VIDEO_IMAGE_SETTINGS)
|
||||
|
||||
Reference in New Issue
Block a user