Merge pull request #7508 from edx/gprice/export-import-val
Include VAL data in video module export/import
This commit is contained in:
@@ -15,20 +15,21 @@ the course, section, subsection, unit, etc.
|
||||
import unittest
|
||||
import datetime
|
||||
from uuid import uuid4
|
||||
from mock import Mock, patch
|
||||
|
||||
from . import LogicTest
|
||||
from lxml import etree
|
||||
from mock import ANY, Mock, patch
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
from xmodule.video_module import VideoDescriptor, create_youtube_string, get_video_from_cdn
|
||||
from .test_import import DummySystem
|
||||
from xblock.field_data import DictFieldData
|
||||
from xblock.fields import ScopeIds
|
||||
|
||||
from xmodule.tests import get_test_descriptor_system
|
||||
from xmodule.video_module import VideoDescriptor, create_youtube_string, get_video_from_cdn
|
||||
from xmodule.video_module.transcripts_utils import download_youtube_subs, save_to_store
|
||||
|
||||
from django.conf import settings
|
||||
from . import LogicTest
|
||||
from .test_import import DummySystem
|
||||
|
||||
SRT_FILEDATA = '''
|
||||
0
|
||||
@@ -89,6 +90,19 @@ def instantiate_descriptor(**field_data):
|
||||
)
|
||||
|
||||
|
||||
# Because of the way xmodule.video_module.video_module imports edxval.api, we
|
||||
# must mock the entire module, which requires making mock exception classes.
|
||||
|
||||
class _MockValVideoNotFoundError(Exception):
|
||||
"""Mock ValVideoNotFoundError exception"""
|
||||
pass
|
||||
|
||||
|
||||
class _MockValCannotCreateError(Exception):
|
||||
"""Mock ValCannotCreateError exception"""
|
||||
pass
|
||||
|
||||
|
||||
class VideoModuleTest(LogicTest):
|
||||
"""Logic tests for Video Xmodule."""
|
||||
descriptor_class = VideoDescriptor
|
||||
@@ -176,6 +190,21 @@ class VideoDescriptorTestBase(unittest.TestCase):
|
||||
super(VideoDescriptorTestBase, self).setUp()
|
||||
self.descriptor = instantiate_descriptor()
|
||||
|
||||
def assertXmlEqual(self, expected, xml):
|
||||
"""
|
||||
Assert that the given XML fragments have the same attributes, text, and
|
||||
(recursively) children
|
||||
"""
|
||||
def get_child_tags(elem):
|
||||
"""Extract the list of tag names for children of elem"""
|
||||
return [child.tag for child in elem]
|
||||
|
||||
for attr in ['tag', 'attrib', 'text', 'tail']:
|
||||
self.assertEqual(getattr(expected, attr), getattr(xml, attr))
|
||||
self.assertEqual(get_child_tags(expected), get_child_tags(xml))
|
||||
for left, right in zip(expected, xml):
|
||||
self.assertXmlEqual(left, right)
|
||||
|
||||
|
||||
class TestCreateYoutubeString(VideoDescriptorTestBase):
|
||||
"""
|
||||
@@ -522,21 +551,61 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
|
||||
'data': ''
|
||||
})
|
||||
|
||||
@patch('xmodule.video_module.video_module.edxval_api')
|
||||
def test_import_val_data(self, mock_val_api):
|
||||
def mock_val_import(xml, edx_video_id):
|
||||
"""Mock edxval.api.import_from_xml"""
|
||||
self.assertEqual(xml.tag, 'video_asset')
|
||||
self.assertEqual(dict(xml.items()), {'mock_attr': ''})
|
||||
self.assertEqual(edx_video_id, 'test_edx_video_id')
|
||||
|
||||
mock_val_api.import_from_xml = Mock(wraps=mock_val_import)
|
||||
module_system = DummySystem(load_error_modules=True)
|
||||
|
||||
# import new edx_video_id
|
||||
xml_data = """
|
||||
<video edx_video_id="test_edx_video_id">
|
||||
<video_asset mock_attr=""/>
|
||||
</video>
|
||||
"""
|
||||
video = VideoDescriptor.from_xml(xml_data, module_system, id_generator=Mock())
|
||||
|
||||
self.assert_attributes_equal(video, {'edx_video_id': 'test_edx_video_id'})
|
||||
mock_val_api.import_from_xml.assert_called_once_with(ANY, 'test_edx_video_id')
|
||||
|
||||
@patch('xmodule.video_module.video_module.edxval_api')
|
||||
def test_import_val_data_invalid(self, mock_val_api):
|
||||
mock_val_api.ValCannotCreateError = _MockValCannotCreateError
|
||||
mock_val_api.import_from_xml = Mock(side_effect=mock_val_api.ValCannotCreateError)
|
||||
module_system = DummySystem(load_error_modules=True)
|
||||
|
||||
# Negative duration is invalid
|
||||
xml_data = """
|
||||
<video edx_video_id="test_edx_video_id">
|
||||
<video_asset client_video_id="test_client_video_id" duration="-1"/>
|
||||
</video>
|
||||
"""
|
||||
with self.assertRaises(mock_val_api.ValCannotCreateError):
|
||||
VideoDescriptor.from_xml(xml_data, module_system, id_generator=Mock())
|
||||
|
||||
|
||||
class VideoExportTestCase(VideoDescriptorTestBase):
|
||||
"""
|
||||
Make sure that VideoDescriptor can export itself to XML correctly.
|
||||
"""
|
||||
def assertXmlEqual(self, expected, xml):
|
||||
for attr in ['tag', 'attrib', 'text', 'tail']:
|
||||
self.assertEqual(getattr(expected, attr), getattr(xml, attr))
|
||||
for left, right in zip(expected, xml):
|
||||
self.assertXmlEqual(left, right)
|
||||
|
||||
def test_export_to_xml(self):
|
||||
@patch('xmodule.video_module.video_module.edxval_api')
|
||||
def test_export_to_xml(self, mock_val_api):
|
||||
"""
|
||||
Test that we write the correct XML on export.
|
||||
"""
|
||||
def mock_val_export(edx_video_id):
|
||||
"""Mock edxval.api.export_to_xml"""
|
||||
return etree.Element( # pylint:disable=no-member
|
||||
'video_asset',
|
||||
attrib={'export_edx_video_id': edx_video_id}
|
||||
)
|
||||
|
||||
mock_val_api.export_to_xml = mock_val_export
|
||||
self.descriptor.youtube_id_0_75 = 'izygArpw-Qo'
|
||||
self.descriptor.youtube_id_1_0 = 'p2Q6BrNhdh8'
|
||||
self.descriptor.youtube_id_1_25 = '1EeWXzPdhSA'
|
||||
@@ -550,6 +619,7 @@ class VideoExportTestCase(VideoDescriptorTestBase):
|
||||
self.descriptor.html5_sources = ['http://www.example.com/source.mp4', 'http://www.example.com/source.ogg']
|
||||
self.descriptor.download_video = True
|
||||
self.descriptor.transcripts = {'ua': 'ukrainian_translation.srt', 'ge': 'german_translation.srt'}
|
||||
self.descriptor.edx_video_id = 'test_edx_video_id'
|
||||
|
||||
xml = self.descriptor.definition_to_xml(None) # We don't use the `resource_fs` parameter
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
@@ -561,11 +631,25 @@ class VideoExportTestCase(VideoDescriptorTestBase):
|
||||
<handout src="http://www.example.com/handout"/>
|
||||
<transcript language="ge" src="german_translation.srt" />
|
||||
<transcript language="ua" src="ukrainian_translation.srt" />
|
||||
<video_asset export_edx_video_id="test_edx_video_id"/>
|
||||
</video>
|
||||
'''
|
||||
expected = etree.XML(xml_string, parser=parser)
|
||||
self.assertXmlEqual(expected, xml)
|
||||
|
||||
@patch('xmodule.video_module.video_module.edxval_api')
|
||||
def test_export_to_xml_val_error(self, mock_val_api):
|
||||
# Export should succeed without VAL data if video does not exist
|
||||
mock_val_api.ValVideoNotFoundError = _MockValVideoNotFoundError
|
||||
mock_val_api.export_to_xml = Mock(side_effect=mock_val_api.ValVideoNotFoundError)
|
||||
self.descriptor.edx_video_id = 'test_edx_video_id'
|
||||
|
||||
xml = self.descriptor.definition_to_xml(None)
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
xml_string = '<video url_name="SampleProblem" download_video="false"/>'
|
||||
expected = etree.XML(xml_string, parser=parser)
|
||||
self.assertXmlEqual(expected, xml)
|
||||
|
||||
def test_export_to_xml_empty_end_time(self):
|
||||
"""
|
||||
Test that we write the correct XML on export.
|
||||
|
||||
@@ -404,8 +404,7 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
|
||||
xml_data: A string of xml that will be translated into data and children for
|
||||
this module
|
||||
system: A DescriptorSystem for interacting with external resources
|
||||
org and course are optional strings that will be used in the generated modules
|
||||
url identifiers
|
||||
id_generator is used to generate course-specific urls and identifiers
|
||||
"""
|
||||
xml_object = etree.fromstring(xml_data)
|
||||
url_name = xml_object.get('url_name', xml_object.get('slug'))
|
||||
@@ -478,6 +477,12 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
|
||||
ele.set('src', self.transcripts[transcript_language])
|
||||
xml.append(ele)
|
||||
|
||||
if self.edx_video_id and edxval_api:
|
||||
try:
|
||||
xml.append(edxval_api.export_to_xml(self.edx_video_id))
|
||||
except edxval_api.ValVideoNotFoundError:
|
||||
pass
|
||||
|
||||
return xml
|
||||
|
||||
def get_context(self):
|
||||
@@ -621,6 +626,15 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
|
||||
if 'download_track' not in field_data and track is not None:
|
||||
field_data['download_track'] = True
|
||||
|
||||
video_asset_elem = xml.find('video_asset')
|
||||
if (
|
||||
edxval_api and
|
||||
video_asset_elem is not None and
|
||||
'edx_video_id' in field_data
|
||||
):
|
||||
# Allow ValCannotCreateError to escape
|
||||
edxval_api.import_from_xml(video_asset_elem, field_data['edx_video_id'])
|
||||
|
||||
return field_data
|
||||
|
||||
def index_dictionary(self):
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -396,7 +396,7 @@ CREATE TABLE `auth_permission` (
|
||||
UNIQUE KEY `content_type_id` (`content_type_id`,`codename`),
|
||||
KEY `auth_permission_e4470c6e` (`content_type_id`),
|
||||
CONSTRAINT `content_type_id_refs_id_728de91f` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=499 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=511 DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `auth_registration`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -623,6 +623,20 @@ CREATE TABLE `certificates_certificategenerationcoursesetting` (
|
||||
KEY `certificates_certificategenerationcoursesetting_b4b47e7a` (`course_key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `certificates_certificatehtmlviewconfiguration`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `certificates_certificatehtmlviewconfiguration` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`change_date` datetime NOT NULL,
|
||||
`changed_by_id` int(11) DEFAULT NULL,
|
||||
`enabled` tinyint(1) NOT NULL,
|
||||
`configuration` longtext NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `certificates_certificatehtmlviewconfiguration_16905482` (`changed_by_id`),
|
||||
CONSTRAINT `changed_by_id_refs_id_8584db17` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `certificates_certificatewhitelist`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
@@ -772,6 +786,31 @@ CREATE TABLE `course_creators_coursecreator` (
|
||||
CONSTRAINT `user_id_refs_id_6a0e6044` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `course_groups_coursecohort`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `course_groups_coursecohort` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`course_user_group_id` int(11) NOT NULL,
|
||||
`assignment_type` varchar(20) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `course_user_group_id` (`course_user_group_id`),
|
||||
CONSTRAINT `course_user_group_id_refs_id_8febc00f` FOREIGN KEY (`course_user_group_id`) REFERENCES `course_groups_courseusergroup` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `course_groups_coursecohortssettings`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `course_groups_coursecohortssettings` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`is_cohorted` tinyint(1) NOT NULL,
|
||||
`course_id` varchar(255) NOT NULL,
|
||||
`cohorted_discussions` longtext,
|
||||
`always_cohort_inline_discussions` tinyint(1) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `course_id` (`course_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `course_groups_courseusergroup`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
@@ -1031,8 +1070,8 @@ CREATE TABLE `django_admin_log` (
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `django_admin_log_fbfc09f1` (`user_id`),
|
||||
KEY `django_admin_log_e4470c6e` (`content_type_id`),
|
||||
CONSTRAINT `user_id_refs_id_c8665aa` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `content_type_id_refs_id_288599e6` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`)
|
||||
CONSTRAINT `content_type_id_refs_id_288599e6` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
|
||||
CONSTRAINT `user_id_refs_id_c8665aa` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `django_comment_client_permission`;
|
||||
@@ -1094,7 +1133,7 @@ CREATE TABLE `django_content_type` (
|
||||
`model` varchar(100) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `app_label` (`app_label`,`model`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=166 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=170 DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `django_openid_auth_association`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -1294,9 +1333,9 @@ DROP TABLE IF EXISTS `edxval_profile`;
|
||||
CREATE TABLE `edxval_profile` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`profile_name` varchar(50) NOT NULL,
|
||||
`extension` varchar(10) NOT NULL,
|
||||
`width` int(10) unsigned NOT NULL,
|
||||
`height` int(10) unsigned NOT NULL,
|
||||
`extension` varchar(10) DEFAULT 'mp4',
|
||||
`width` int(10) unsigned DEFAULT '1',
|
||||
`height` int(10) unsigned DEFAULT '1',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `profile_name` (`profile_name`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
|
||||
@@ -1650,6 +1689,20 @@ CREATE TABLE `milestones_usermilestone` (
|
||||
CONSTRAINT `milestone_id_refs_id_af7fa460` FOREIGN KEY (`milestone_id`) REFERENCES `milestones_milestone` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `mobile_api_mobileapiconfig`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `mobile_api_mobileapiconfig` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`change_date` datetime NOT NULL,
|
||||
`changed_by_id` int(11) DEFAULT NULL,
|
||||
`enabled` tinyint(1) NOT NULL,
|
||||
`video_profiles` longtext NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `mobile_api_mobileapiconfig_16905482` (`changed_by_id`),
|
||||
CONSTRAINT `changed_by_id_refs_id_97c2f4c8` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `notes_note`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
@@ -2245,7 +2298,7 @@ CREATE TABLE `south_migrationhistory` (
|
||||
`migration` varchar(255) NOT NULL,
|
||||
`applied` datetime NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=222 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=228 DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `splash_splashconfig`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
|
||||
Reference in New Issue
Block a user