Merge pull request #20384 from open-craft/symbolist/video-xblock

VideoModule to VideoBlock [SE-602]
This commit is contained in:
Feanil Patel
2019-06-27 16:23:00 -04:00
committed by GitHub
34 changed files with 379 additions and 454 deletions

View File

@@ -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)

View File

@@ -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'
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -34,7 +34,7 @@
}
}
.xmodule_VideoDescriptor {
.xmodule_VideoBlock {
.wrapper-comp-settings.basic_metadata_edit {
.list-input.settings-list {
.field.comp-setting-entry {

View File

@@ -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 = [

View File

@@ -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

View File

@@ -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

View File

@@ -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,
]

View File

@@ -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)

View File

@@ -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="&quot;OEoXaMPEzf10&quot;"
/>
'''
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:&quot;p2Q6BrNhdh8&quot;,1.25:&quot;1EeWXzPdhSA&quot;">
</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):

View File

@@ -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,
}

View File

@@ -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': '',

View File

@@ -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.

View File

@@ -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:

View File

@@ -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"])

View File

@@ -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(

View File

@@ -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"),

View File

@@ -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',

View File

@@ -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"]',

View 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"
)

View File

@@ -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

View File

@@ -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'])

View File

@@ -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.

View File

@@ -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):
"""

View File

@@ -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

View File

@@ -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': '',

View File

@@ -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)