Merge pull request #7508 from edx/gprice/export-import-val

Include VAL data in video module export/import
This commit is contained in:
Greg Price
2015-03-31 20:28:31 -04:00
8 changed files with 267 additions and 70 deletions

View File

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

View File

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

View File

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