<% if (newLang) {
+ %>
<%= value ? gettext("Replace") : gettext("Upload") %>
<%
} %><% if (value) {
- %>
<%= gettext("Download") %>
+ %><%= gettext("Download") %>
<%
}
- %>
+ %>
+
diff --git a/common/lib/xmodule/xmodule/video_module/video_handlers.py b/common/lib/xmodule/xmodule/video_module/video_handlers.py
index 7c8f1560cf..19b17f2dd9 100644
--- a/common/lib/xmodule/xmodule/video_module/video_handlers.py
+++ b/common/lib/xmodule/xmodule/video_module/video_handlers.py
@@ -7,9 +7,9 @@ StudioViewHandlers are handlers for video descriptor instance.
import json
import logging
-import os
import six
+from django.core.files.base import ContentFile
from django.utils.timezone import now
from webob import Response
@@ -20,11 +20,11 @@ from xmodule.exceptions import NotFoundError
from xmodule.fields import RelativeTime
from opaque_keys.edx.locator import CourseLocator
+from edxval.api import create_or_update_video_transcript, create_external_video, delete_video_transcript
from .transcripts_utils import (
- convert_video_transcript,
+ clean_video_id,
get_or_create_sjson,
generate_sjson_for_all_speeds,
- get_video_transcript_content,
save_to_store,
subs_filename,
Transcript,
@@ -32,10 +32,9 @@ from .transcripts_utils import (
TranscriptsGenerationException,
youtube_speed_dict,
get_transcript,
- get_transcript_from_contentstore
-)
-from .transcripts_model_utils import (
- is_val_transcript_feature_enabled_for_course
+ get_transcript_from_contentstore,
+ remove_subs_from_store,
+ get_html5_ids
)
log = logging.getLogger(__name__)
@@ -381,6 +380,38 @@ class VideoStudioViewHandlers(object):
"""
Handlers for Studio view.
"""
+ def validate_transcript_upload_data(self, data):
+ """
+ Validates video transcript file.
+ Arguments:
+ data: Transcript data to be validated.
+ Returns:
+ None or String
+ If there is error returns error message otherwise None.
+ """
+ error = None
+ _ = self.runtime.service(self, "i18n").ugettext
+ # Validate the must have attributes - this error is unlikely to be faced by common users.
+ must_have_attrs = ['edx_video_id', 'language_code', 'new_language_code']
+ missing = [attr for attr in must_have_attrs if attr not in data]
+
+ # Get available transcript languages.
+ transcripts = self.get_transcripts_info()
+ available_translations = self.available_translations(transcripts, verify_assets=True)
+
+ if missing:
+ error = _(u'The following parameters are required: {missing}.').format(missing=', '.join(missing))
+ elif (
+ data['language_code'] != data['new_language_code'] and data['new_language_code'] in available_translations
+ ):
+ error = _(u'A transcript with the "{language_code}" language code already exists.'.format(
+ language_code=data['new_language_code']
+ ))
+ elif 'file' not in data:
+ error = _(u'A transcript file is required.')
+
+ return error
+
@XBlock.handler
def studio_transcript(self, request, dispatch):
"""
@@ -412,39 +443,104 @@ class VideoStudioViewHandlers(object):
_ = self.runtime.service(self, "i18n").ugettext
if dispatch.startswith('translation'):
- language = dispatch.replace('translation', '').strip('/')
-
- if not language:
- log.info("Invalid /translation request: no language.")
- return Response(status=400)
if request.method == 'POST':
- subtitles = request.POST['file']
- try:
- file_data = subtitles.file.read()
- unicode(file_data, "utf-8", "strict")
- except UnicodeDecodeError:
- log.info("Invalid encoding type for transcript file: {}".format(subtitles.filename))
- msg = _("Invalid encoding type, transcripts should be UTF-8 encoded.")
- return Response(msg, status=400)
- save_to_store(file_data, unicode(subtitles.filename), 'application/x-subrip', self.location)
- generate_sjson_for_all_speeds(self, unicode(subtitles.filename), {}, language)
- response = {'filename': unicode(subtitles.filename), 'status': 'Success'}
- return Response(json.dumps(response), status=201)
+ error = self.validate_transcript_upload_data(data=request.POST)
+ if error:
+ response = Response(json={'error': error}, status=400)
+ else:
+ edx_video_id = clean_video_id(request.POST['edx_video_id'])
+ language_code = request.POST['language_code']
+ new_language_code = request.POST['new_language_code']
+ transcript_file = request.POST['file'].file
- elif request.method == 'GET':
+ if not edx_video_id:
+ # Back-populate the video ID for an external video.
+ # pylint: disable=attribute-defined-outside-init
+ self.edx_video_id = edx_video_id = create_external_video(display_name=u'external video')
- filename = request.GET.get('filename')
- if not filename:
- log.info("Invalid /translation request: no filename in request.GET")
+ try:
+ # Convert SRT transcript into an SJSON format
+ # and upload it to S3.
+ sjson_subs = Transcript.convert(
+ content=transcript_file.read(),
+ input_format=Transcript.SRT,
+ output_format=Transcript.SJSON
+ )
+ create_or_update_video_transcript(
+ video_id=edx_video_id,
+ language_code=language_code,
+ metadata={
+ 'file_format': Transcript.SJSON,
+ 'language_code': new_language_code
+ },
+ file_data=ContentFile(sjson_subs),
+ )
+ payload = {
+ 'edx_video_id': edx_video_id,
+ 'language_code': new_language_code
+ }
+ response = Response(json.dumps(payload), status=201)
+ except (TranscriptsGenerationException, UnicodeDecodeError):
+ response = Response(
+ json={
+ 'error': _(
+ u'There is a problem with this transcript file. Try to upload a different file.'
+ )
+ },
+ status=400
+ )
+ elif request.method == 'DELETE':
+ request_data = request.json
+
+ if 'lang' not in request_data or 'edx_video_id' not in request_data:
return Response(status=400)
- content = Transcript.get_asset(self.location, filename).data
- response = Response(content, headerlist=[
- ('Content-Disposition', 'attachment; filename="{}"'.format(filename.encode('utf8'))),
- ('Content-Language', language),
- ])
- response.content_type = Transcript.mime_types['srt']
+ language = request_data['lang']
+ edx_video_id = clean_video_id(request_data['edx_video_id'])
+
+ if edx_video_id:
+ delete_video_transcript(video_id=edx_video_id, language_code=language)
+
+ if language == u'en':
+ # remove any transcript file from content store for the video ids
+ possible_sub_ids = [
+ self.sub, # pylint: disable=access-member-before-definition
+ self.youtube_id_1_0
+ ] + get_html5_ids(self.html5_sources)
+ for sub_id in possible_sub_ids:
+ remove_subs_from_store(sub_id, self, language)
+
+ # update metadata as `en` can also be present in `transcripts` field
+ remove_subs_from_store(self.transcripts.pop(language, None), self, language)
+
+ # also empty `sub` field
+ self.sub = '' # pylint: disable=attribute-defined-outside-init
+ else:
+ remove_subs_from_store(self.transcripts.pop(language, None), self, language)
+
+ return Response(status=200)
+
+ elif request.method == 'GET':
+ language = request.GET.get('language_code')
+ if not language:
+ return Response(json={'error': _(u'Language is required.')}, status=400)
+
+ try:
+ transcript_content, transcript_name, mime_type = get_transcript(
+ video=self, lang=language, output_format=Transcript.SRT
+ )
+ response = Response(transcript_content, headerlist=[
+ ('Content-Disposition', 'attachment; filename="{}"'.format(transcript_name.encode('utf8'))),
+ ('Content-Language', language),
+ ('Content-Type', mime_type)
+ ])
+ except (UnicodeDecodeError, TranscriptsGenerationException, NotFoundError):
+ response = Response(status=404)
+
+ else:
+ # Any other HTTP method is not allowed.
+ response = Response(status=404)
else: # unknown dispatch
log.debug("Dispatch is not allowed")
diff --git a/common/lib/xmodule/xmodule/video_module/video_module.py b/common/lib/xmodule/xmodule/video_module/video_module.py
index f29df60967..4457965524 100644
--- a/common/lib/xmodule/xmodule/video_module/video_module.py
+++ b/common/lib/xmodule/xmodule/video_module/video_module.py
@@ -49,6 +49,7 @@ from .transcripts_utils import (
VideoTranscriptsMixin,
clean_video_id,
subs_filename,
+ get_transcript_for_video
)
from .transcripts_model_utils import (
is_val_transcript_feature_enabled_for_course
@@ -611,8 +612,27 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
languages = [{'label': label, 'code': lang} for lang, label in settings.ALL_LANGUAGES]
languages.sort(key=lambda l: l['label'])
+ editable_fields['transcripts']['custom'] = True
editable_fields['transcripts']['languages'] = languages
editable_fields['transcripts']['type'] = 'VideoTranslations'
+
+ # construct transcripts info and also find if `en` subs exist
+ transcripts_info = self.get_transcripts_info()
+ possible_sub_ids = [self.sub, self.youtube_id_1_0] + get_html5_ids(self.html5_sources)
+ for sub_id in possible_sub_ids:
+ try:
+ get_transcript_for_video(
+ self.location,
+ subs_id=sub_id,
+ file_name=sub_id,
+ language=u'en'
+ )
+ transcripts_info['transcripts'] = dict(transcripts_info['transcripts'], en=sub_id)
+ break
+ except NotFoundError:
+ continue
+
+ editable_fields['transcripts']['value'] = transcripts_info['transcripts']
editable_fields['transcripts']['urlRoot'] = self.runtime.handler_url(
self,
'studio_transcript',
diff --git a/common/test/acceptance/pages/studio/video/video.py b/common/test/acceptance/pages/studio/video/video.py
index 5f42a7d21f..58abc54c85 100644
--- a/common/test/acceptance/pages/studio/video/video.py
+++ b/common/test/acceptance/pages/studio/video/video.py
@@ -81,6 +81,11 @@ DEFAULT_SETTINGS = [
['YouTube ID for 1.5x speed', '', False]
]
+# field names without clear button
+FIELDS_WO_CLEAR = [
+ 'Transcript Languages'
+]
+
# We should wait 300 ms for event handler invocation + 200ms for safety.
DELAY = 0.5
@@ -346,15 +351,22 @@ class VideoComponentPage(VideoPage):
"""
Verify that video component has correct default settings.
"""
- query = '.wrapper-comp-setting'
- settings = self.q(css=query).results
- if len(DEFAULT_SETTINGS) != len(settings):
- return False
+ def _check_settings_length():
+ """Check video settings"""
+ query = '.wrapper-comp-setting'
+ settings = self.q(css=query).results
+ if len(DEFAULT_SETTINGS) == len(settings):
+ return True, settings
+ return (False, None)
+
+ settings = Promise(_check_settings_length, 'All video fields are present').fulfill()
for counter, setting in enumerate(settings):
- is_verified = self._verify_setting_entry(setting,
- DEFAULT_SETTINGS[counter][0],
- DEFAULT_SETTINGS[counter][1])
+ is_verified = self._verify_setting_entry(
+ setting,
+ DEFAULT_SETTINGS[counter][0],
+ DEFAULT_SETTINGS[counter][1]
+ )
if not is_verified:
return is_verified
@@ -395,9 +407,8 @@ class VideoComponentPage(VideoPage):
if field_value != current_value:
return False
- # Clear button should be visible(active class is present) for
- # every setting that don't have 'metadata-videolist-enum' class
- if 'metadata-videolist-enum' not in setting.get_attribute('class'):
+ # Verify if clear button is active for expected video fields
+ if field_name not in FIELDS_WO_CLEAR and 'metadata-videolist-enum' not in setting.get_attribute('class'):
setting_clear_button = setting.find_elements_by_class_name('setting-clear')[0]
if 'active' not in setting_clear_button.get_attribute('class'):
return False
@@ -513,8 +524,8 @@ class VideoComponentPage(VideoPage):
list: list of translation language codes
"""
- translations_selector = '.metadata-video-translations .remove-setting'
- return self.q(css=translations_selector).attrs('data-lang')
+ translations_selector = '.metadata-video-translations .list-settings-item'
+ return self.q(css=translations_selector).attrs('data-original-lang')
def download_translation(self, language_code, text_to_search):
"""
@@ -529,7 +540,7 @@ class VideoComponentPage(VideoPage):
"""
mime_type = 'application/x-subrip'
- lang_code = '/{}?'.format(language_code)
+ lang_code = '?language_code={}'.format(language_code)
link = [link for link in self.q(css='.download-action').attrs('href') if lang_code in link]
result, headers, content = self._get_transcript(link[0])
@@ -543,7 +554,9 @@ class VideoComponentPage(VideoPage):
language_code (str): language code
"""
- self.q(css='.remove-action').filter(lambda el: language_code == el.get_attribute('data-lang')).click()
+ selector = '.metadata-video-translations .list-settings-item'
+ translation = self.q(css=selector).filter(lambda el: language_code == el.get_attribute('data-original-lang'))
+ translation[0].find_element_by_class_name('remove-action').click()
@property
def upload_status_message(self):
diff --git a/common/test/acceptance/tests/video/test_studio_video_editor.py b/common/test/acceptance/tests/video/test_studio_video_editor.py
index 73c2d126b5..cd1a409a02 100644
--- a/common/test/acceptance/tests/video/test_studio_video_editor.py
+++ b/common/test/acceptance/tests/video/test_studio_video_editor.py
@@ -3,12 +3,14 @@
"""
Acceptance tests for CMS Video Editor.
"""
+import ddt
from nose.plugins.attrib import attr
-
+from common.test.acceptance.pages.common.utils import confirm_prompt
from common.test.acceptance.tests.video.test_studio_video_module import CMSVideoBaseTest
@attr(shard=6)
+@ddt.ddt
class VideoEditorTest(CMSVideoBaseTest):
"""
CMS Video Editor Test Class
@@ -263,6 +265,7 @@ class VideoEditorTest(CMSVideoBaseTest):
self.open_advanced_tab()
self.assertEqual(self.video.translations(), ['zh', 'uk'])
self.video.remove_translation('uk')
+ confirm_prompt(self.video)
self.save_unit_settings()
self.assertTrue(self.video.is_captions_visible())
unicode_text = "好 各位同学".decode('utf-8')
@@ -271,6 +274,7 @@ class VideoEditorTest(CMSVideoBaseTest):
self.open_advanced_tab()
self.assertEqual(self.video.translations(), ['zh'])
self.video.remove_translation('zh')
+ confirm_prompt(self.video)
self.save_unit_settings()
self.assertFalse(self.video.is_captions_visible())
@@ -292,69 +296,27 @@ class VideoEditorTest(CMSVideoBaseTest):
self.video.upload_translation('uk_transcripts.srt', 'uk')
self.assertEqual(self.video.translations(), ['uk'])
self.video.remove_translation('uk')
+ confirm_prompt(self.video)
self.save_unit_settings()
self.assertFalse(self.video.is_captions_visible())
- def test_translations_clearing_works_w_saving(self):
+ def test_translations_entry_remove_works(self):
"""
- Scenario: Translations clearing works correctly w/ preliminary saving
+ Scenario: Translations entry removal works correctly when transcript is not uploaded
Given I have created a Video component
And I edit the component
And I open tab "Advanced"
- And I upload transcript files:
- |lang_code|filename |
- |uk |uk_transcripts.srt |
- |zh |chinese_transcripts.srt|
- And I save changes
- Then when I view the video it does show the captions
- And I see "Привіт, edX вітає вас." text in the captions
- And video language menu has "uk, zh" translations
- And I edit the component
- And I open tab "Advanced"
- And I see translations for "uk, zh"
- And I click button "Clear"
- And I save changes
- Then when I view the video it does not show the captions
+ And I click on "+ Add" button for "Transcript Languages" field
+ Then I click on "Remove" button
+ And I see newly created entry is removed
"""
self._create_video_component()
self.edit_component()
self.open_advanced_tab()
- self.video.upload_translation('uk_transcripts.srt', 'uk')
- self.video.upload_translation('chinese_transcripts.srt', 'zh')
- self.save_unit_settings()
- self.assertTrue(self.video.is_captions_visible())
- unicode_text = "Привіт, edX вітає вас.".decode('utf-8')
- self.assertIn(unicode_text, self.video.captions_text)
- self.assertEqual(self.video.caption_languages.keys(), ['zh', 'uk'])
- self.edit_component()
- self.open_advanced_tab()
- self.assertEqual(self.video.translations(), ['zh', 'uk'])
- self.video.click_button('translations_clear')
- self.save_unit_settings()
- self.assertFalse(self.video.is_captions_visible())
-
- def test_translations_clearing_works_wo_saving(self):
- """
- Scenario: Translations clearing works correctly w/o preliminary saving
- Given I have created a Video component
- And I edit the component
- And I open tab "Advanced"
- And I upload transcript files:
- |lang_code|filename |
- |uk |uk_transcripts.srt |
- |zh |chinese_transcripts.srt|
- And I click button "Clear"
- And I save changes
- Then when I view the video it does not show the captions
- """
- self._create_video_component()
- self.edit_component()
- self.open_advanced_tab()
- self.video.upload_translation('uk_transcripts.srt', 'uk')
- self.video.upload_translation('chinese_transcripts.srt', 'zh')
- self.video.click_button('translations_clear')
- self.save_unit_settings()
- self.assertFalse(self.video.is_captions_visible())
+ self.video.click_button("translation_add")
+ self.assertEqual(self.video.translations_count(), 1)
+ self.video.remove_translation("")
+ self.assertEqual(self.video.translations_count(), 0)
def test_cannot_upload_sjson_translation(self):
"""
@@ -455,6 +417,7 @@ class VideoEditorTest(CMSVideoBaseTest):
self.video.upload_translation('chinese_transcripts.srt', 'zh')
self.assertEqual(self.video.translations(), ['zh'])
self.video.remove_translation('zh')
+ confirm_prompt(self.video)
self.video.upload_translation('uk_transcripts.srt', 'zh')
self.save_unit_settings()
self.assertTrue(self.video.is_captions_visible())
diff --git a/lms/djangoapps/courseware/tests/test_video_handlers.py b/lms/djangoapps/courseware/tests/test_video_handlers.py
index 4829b0590d..adbb4d2d66 100644
--- a/lms/djangoapps/courseware/tests/test_video_handlers.py
+++ b/lms/djangoapps/courseware/tests/test_video_handlers.py
@@ -9,6 +9,7 @@ from datetime import timedelta
import ddt
import freezegun
+from django.core.files.base import ContentFile
from django.utils.timezone import now
from mock import MagicMock, Mock, patch
from nose.plugins.attrib import attr
@@ -21,9 +22,15 @@ 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.transcripts_utils import TranscriptException, TranscriptsGenerationException, Transcript
+from xmodule.video_module.transcripts_utils import (
+ Transcript,
+ edxval_api,
+ subs_filename,
+)
from xmodule.x_module import STUDENT_VIEW
+from edxval import api
+
from .helpers import BaseTestXmodule
from .test_video_xml import SOURCE_XML
@@ -863,42 +870,44 @@ class TestStudioTranscriptTranslationGetDispatch(TestVideo):
def test_translation_fails(self):
# No language
- request = Request.blank('')
- response = self.item_descriptor.studio_transcript(request=request, dispatch='translation')
- self.assertEqual(response.status, '400 Bad Request')
+ request = Request.blank("")
+ response = self.item_descriptor.studio_transcript(request=request, dispatch="translation")
+ self.assertEqual(response.status, "400 Bad Request")
- # No filename in request.GET
- request = Request.blank('')
- response = self.item_descriptor.studio_transcript(request=request, dispatch='translation/uk')
- self.assertEqual(response.status, '400 Bad Request')
+ # No language_code param in request.GET
+ request = Request.blank("")
+ response = self.item_descriptor.studio_transcript(request=request, dispatch="translation")
+ self.assertEqual(response.status, "400 Bad Request")
+ self.assertEqual(response.json["error"], "Language is required.")
# Correct case:
filename = os.path.split(self.srt_file.name)[1]
_upload_file(self.srt_file, self.item_descriptor.location, filename)
+ request = Request.blank(u"translation?language_code=uk")
+ response = self.item_descriptor.studio_transcript(request=request, dispatch="translation?language_code=uk")
self.srt_file.seek(0)
- request = Request.blank(u'translation/uk?filename={}'.format(filename))
- response = self.item_descriptor.studio_transcript(request=request, dispatch='translation/uk')
self.assertEqual(response.body, self.srt_file.read())
- self.assertEqual(response.headers['Content-Type'], 'application/x-subrip; charset=utf-8')
+ self.assertEqual(response.headers["Content-Type"], "application/x-subrip; charset=utf-8")
self.assertEqual(
- response.headers['Content-Disposition'],
- 'attachment; filename="{}"'.format(filename)
+ response.headers["Content-Disposition"],
+ 'attachment; filename="uk_{}"'.format(filename)
)
- self.assertEqual(response.headers['Content-Language'], 'uk')
+ self.assertEqual(response.headers["Content-Language"], "uk")
# Non ascii file name download:
self.srt_file.seek(0)
- _upload_file(self.srt_file, self.item_descriptor.location, u'塞.srt')
+ _upload_file(self.srt_file, self.item_descriptor.location, u"塞.srt")
+ request = Request.blank("translation?language_code=zh")
+ response = self.item_descriptor.studio_transcript(request=request, dispatch="translation?language_code=zh")
self.srt_file.seek(0)
- request = Request.blank('translation/zh?filename={}'.format(u'塞.srt'.encode('utf8')))
- response = self.item_descriptor.studio_transcript(request=request, dispatch='translation/zh')
self.assertEqual(response.body, self.srt_file.read())
- self.assertEqual(response.headers['Content-Type'], 'application/x-subrip; charset=utf-8')
- self.assertEqual(response.headers['Content-Disposition'], 'attachment; filename="塞.srt"')
- self.assertEqual(response.headers['Content-Language'], 'zh')
+ self.assertEqual(response.headers["Content-Type"], "application/x-subrip; charset=utf-8")
+ self.assertEqual(response.headers["Content-Disposition"], 'attachment; filename="zh_塞.srt"')
+ self.assertEqual(response.headers["Content-Language"], "zh")
@attr(shard=1)
+@ddt.ddt
class TestStudioTranscriptTranslationPostDispatch(TestVideo):
"""
Test Studio video handler that provide translation transcripts.
@@ -921,42 +930,219 @@ class TestStudioTranscriptTranslationPostDispatch(TestVideo):
METADATA = {}
- def test_studio_transcript_post(self):
- # Check for exceptons:
-
- # Language is passed, bad content or filename:
-
- # should be first, as other tests save transcrips to store.
- request = Request.blank('/translation/uk', POST={'file': ('filename.srt', SRT_content)})
- with patch('xmodule.video_module.video_handlers.save_to_store'):
- with self.assertRaises(TranscriptException): # transcripts were not saved to store for some reason.
- response = self.item_descriptor.studio_transcript(request=request, dispatch='translation/uk')
- request = Request.blank('/translation/uk', POST={'file': ('filename', 'content')})
- with self.assertRaises(TranscriptsGenerationException): # Not an srt filename
- self.item_descriptor.studio_transcript(request=request, dispatch='translation/uk')
-
- request = Request.blank('/translation/uk', POST={'file': ('filename.srt', 'content')})
- with self.assertRaises(TranscriptsGenerationException): # Content format is not srt.
- response = self.item_descriptor.studio_transcript(request=request, dispatch='translation/uk')
-
- request = Request.blank('/translation/uk', POST={'file': ('filename.srt', SRT_content.decode('utf8').encode('cp1251'))})
- # Non-UTF8 file content encoding.
- response = self.item_descriptor.studio_transcript(request=request, dispatch='translation/uk')
- self.assertEqual(response.status_code, 400)
- self.assertEqual(response.body, "Invalid encoding type, transcripts should be UTF-8 encoded.")
-
- # No language is passed.
- request = Request.blank('/translation', POST={'file': ('filename', SRT_content)})
+ @ddt.data(
+ {
+ "post_data": {},
+ "error_message": "The following parameters are required: edx_video_id, language_code, new_language_code."
+ },
+ {
+ "post_data": {"edx_video_id": "111", "language_code": "ar", "new_language_code": "ur"},
+ "error_message": 'A transcript with the "ur" language code already exists.'
+ },
+ {
+ "post_data": {"edx_video_id": "111", "language_code": "ur", "new_language_code": "ur"},
+ "error_message": "A transcript file is required."
+ },
+ )
+ @ddt.unpack
+ def test_studio_transcript_post_validations(self, post_data, error_message):
+ """
+ Verify that POST request validations works as expected.
+ """
+ # mock available_translations method
+ self.item_descriptor.available_translations = lambda transcripts, verify_assets: ['ur']
+ request = Request.blank('/translation', POST=post_data)
response = self.item_descriptor.studio_transcript(request=request, dispatch='translation')
- self.assertEqual(response.status, '400 Bad Request')
+ self.assertEqual(response.json["error"], error_message)
- # Language, good filename and good content.
- request = Request.blank('/translation/uk', POST={'file': ('filename.srt', SRT_content)})
- response = self.item_descriptor.studio_transcript(request=request, dispatch='translation/uk')
+ @ddt.data(
+ {
+ "edx_video_id": "",
+ },
+ {
+ "edx_video_id": "1234-5678-90",
+ },
+ )
+ @ddt.unpack
+ def test_studio_transcript_post_w_no_edx_video_id(self, edx_video_id):
+ """
+ Verify that POST request works as expected
+ """
+ post_data = {
+ "edx_video_id": edx_video_id,
+ "language_code": "ar",
+ "new_language_code": "uk",
+ "file": ("filename.srt", SRT_content)
+ }
+
+ if edx_video_id:
+ edxval_api.create_video({
+ "edx_video_id": edx_video_id,
+ "status": "uploaded",
+ "client_video_id": "a video",
+ "duration": 0,
+ "encoded_videos": [],
+ "courses": []
+ })
+
+ request = Request.blank('/translation', POST=post_data)
+ response = self.item_descriptor.studio_transcript(request=request, dispatch='translation')
self.assertEqual(response.status, '201 Created')
- self.assertDictEqual(json.loads(response.body), {'filename': u'filename.srt', 'status': 'Success'})
+ response = json.loads(response.body)
+ self.assertTrue(response["language_code"], "uk")
self.assertDictEqual(self.item_descriptor.transcripts, {})
- self.assertTrue(_check_asset(self.item_descriptor.location, u'filename.srt'))
+ self.assertTrue(edxval_api.get_video_transcript_data(video_id=response["edx_video_id"], language_code="uk"))
+
+ def test_studio_transcript_post_bad_content(self):
+ """
+ Verify that transcript content encode/decode errors handled as expected
+ """
+ post_data = {
+ "edx_video_id": "",
+ "language_code": "ar",
+ "new_language_code": "uk",
+ "file": ("filename.srt", SRT_content.decode("utf8").encode("cp1251"))
+ }
+
+ request = Request.blank("/translation", POST=post_data)
+ response = self.item_descriptor.studio_transcript(request=request, dispatch="translation")
+ self.assertEqual(response.status_code, 400)
+ self.assertEqual(
+ response.json["error"],
+ "There is a problem with this transcript file. Try to upload a different file."
+ )
+
+
+@attr(shard=1)
+@ddt.ddt
+class TestStudioTranscriptTranslationDeleteDispatch(TestVideo):
+ """
+ Test studio video handler that provide translation transcripts.
+
+ Tests for `translation` dispatch DELETE HTTP method.
+ """
+ EDX_VIDEO_ID, LANGUAGE_CODE_UK, LANGUAGE_CODE_EN = u'an_edx_video_id', u'uk', u'en'
+ REQUEST_META = {'wsgi.url_scheme': 'http', 'REQUEST_METHOD': 'DELETE'}
+ SRT_FILE = _create_srt_file()
+
+ @ddt.data(
+ {
+ 'params': {'lang': 'uk'}
+ },
+ {
+ 'params': {'edx_video_id': '12345'}
+ },
+ {
+ 'params': {}
+ },
+ )
+ @ddt.unpack
+ def test_translation_missing_required_params(self, params):
+ """
+ Verify that DELETE dispatch works as expected when required args are missing from request
+ """
+ request = Request(self.REQUEST_META, body=json.dumps(params))
+ response = self.item_descriptor.studio_transcript(request=request, dispatch='translation')
+ self.assertEqual(response.status_code, 400)
+
+ def test_translation_delete_w_edx_video_id(self):
+ """
+ Verify that DELETE dispatch works as expected when video has edx_video_id
+ """
+ request_body = json.dumps({'lang': self.LANGUAGE_CODE_UK, 'edx_video_id': self.EDX_VIDEO_ID})
+ api.create_video({
+ 'edx_video_id': self.EDX_VIDEO_ID,
+ 'status': 'upload',
+ 'client_video_id': 'awesome.mp4',
+ 'duration': 0,
+ 'encoded_videos': [],
+ 'courses': [unicode(self.course.id)]
+ })
+ api.create_video_transcript(
+ video_id=self.EDX_VIDEO_ID,
+ language_code=self.LANGUAGE_CODE_UK,
+ file_format='srt',
+ content=ContentFile(SRT_content)
+ )
+
+ # verify that a video transcript exists for expected data
+ self.assertTrue(api.get_video_transcript_data(video_id=self.EDX_VIDEO_ID, language_code=self.LANGUAGE_CODE_UK))
+
+ request = Request(self.REQUEST_META, body=request_body)
+ self.item_descriptor.edx_video_id = self.EDX_VIDEO_ID
+ response = self.item_descriptor.studio_transcript(request=request, dispatch='translation')
+ self.assertEqual(response.status_code, 200)
+
+ # verify that a video transcript dose not exist for expected data
+ self.assertFalse(api.get_video_transcript_data(video_id=self.EDX_VIDEO_ID, language_code=self.LANGUAGE_CODE_UK))
+
+ def test_translation_delete_wo_edx_video_id(self):
+ """
+ Verify that DELETE dispatch works as expected when video has no edx_video_id
+ """
+ request_body = json.dumps({'lang': self.LANGUAGE_CODE_UK, 'edx_video_id': ''})
+ srt_file_name_uk = subs_filename('ukrainian_translation.srt', lang=self.LANGUAGE_CODE_UK)
+ request = Request(self.REQUEST_META, body=request_body)
+
+ # upload and verify that srt file exists in assets
+ _upload_file(self.SRT_FILE, self.item_descriptor.location, srt_file_name_uk)
+ self.assertTrue(_check_asset(self.item_descriptor.location, srt_file_name_uk))
+
+ # verify transcripts field
+ self.assertNotEqual(self.item_descriptor.transcripts, {})
+ self.assertTrue(self.LANGUAGE_CODE_UK in self.item_descriptor.transcripts)
+
+ # make request and verify response
+ response = self.item_descriptor.studio_transcript(request=request, dispatch='translation')
+ self.assertEqual(response.status_code, 200)
+
+ # verify that srt file is deleted
+ self.assertEqual(self.item_descriptor.transcripts, {})
+ self.assertFalse(_check_asset(self.item_descriptor.location, srt_file_name_uk))
+
+ def test_translation_delete_w_english_lang(self):
+ """
+ Verify that DELETE dispatch works as expected for english language translation
+ """
+ request_body = json.dumps({'lang': self.LANGUAGE_CODE_EN, 'edx_video_id': ''})
+ srt_file_name_en = subs_filename('english_translation.srt', lang=self.LANGUAGE_CODE_EN)
+ self.item_descriptor.transcripts['en'] = 'english_translation.srt'
+ request = Request(self.REQUEST_META, body=request_body)
+
+ # upload and verify that srt file exists in assets
+ _upload_file(self.SRT_FILE, self.item_descriptor.location, srt_file_name_en)
+ self.assertTrue(_check_asset(self.item_descriptor.location, srt_file_name_en))
+
+ # make request and verify response
+ response = self.item_descriptor.studio_transcript(request=request, dispatch='translation')
+ self.assertEqual(response.status_code, 200)
+
+ # verify that srt file is deleted
+ self.assertTrue(self.LANGUAGE_CODE_EN not in self.item_descriptor.transcripts)
+ self.assertFalse(_check_asset(self.item_descriptor.location, srt_file_name_en))
+
+ def test_translation_delete_w_sub(self):
+ """
+ Verify that DELETE dispatch works as expected when translation is present against `sub` field
+ """
+ request_body = json.dumps({'lang': self.LANGUAGE_CODE_EN, 'edx_video_id': ''})
+ sub_file_name = subs_filename(self.item_descriptor.sub, lang=self.LANGUAGE_CODE_EN)
+ request = Request(self.REQUEST_META, body=request_body)
+
+ # sub should not be empy
+ self.assertFalse(self.item_descriptor.sub == u'')
+
+ # upload and verify that srt file exists in assets
+ _upload_file(self.SRT_FILE, self.item_descriptor.location, sub_file_name)
+ self.assertTrue(_check_asset(self.item_descriptor.location, sub_file_name))
+
+ # make request and verify response
+ response = self.item_descriptor.studio_transcript(request=request, dispatch='translation')
+ self.assertEqual(response.status_code, 200)
+
+ # verify that sub is empty and transcript is deleted also
+ self.assertTrue(self.item_descriptor.sub == u'')
+ self.assertFalse(_check_asset(self.item_descriptor.location, sub_file_name))
@attr(shard=1)