updload/download/replace transcripts

EDUCATOR-2167
EDUCATOR-2168
EDUCATOR-2169
This commit is contained in:
Qubad786
2018-04-01 16:02:17 +05:00
committed by muhammad-ammar
parent 26a1f4506f
commit cadd4378c7
8 changed files with 356 additions and 195 deletions

View File

@@ -13,11 +13,14 @@ define(['jquery', 'underscore', 'gettext', 'js/views/modals/base_modal', 'jquery
viewSpecificClasses: 'confirm'
}),
initialize: function() {
initialize: function(options) {
BaseModal.prototype.initialize.call(this);
this.template = this.loadTemplate('upload-dialog');
this.listenTo(this.model, 'change', this.renderContents);
this.options.title = this.model.get('title');
// `uploadData` can contain extra data that
// can be POSTed along with the file.
this.uploadData = _.extend({}, options.uploadData);
},
addActionButtons: function() {
@@ -73,17 +76,19 @@ define(['jquery', 'underscore', 'gettext', 'js/views/modals/base_modal', 'jquery
},
upload: function(e) {
var uploadAjaxData = _.extend({}, this.uploadData);
// don't show the generic error notification; we're in a modal,
// and we're better off modifying it instead.
uploadAjaxData['notifyOnError'] = false;
if (e && e.preventDefault) { e.preventDefault(); }
this.model.set('uploading', true);
this.$('form').ajaxSubmit({
success: _.bind(this.success, this),
error: _.bind(this.error, this),
uploadProgress: _.bind(this.progress, this),
data: {
// don't show the generic error notification; we're in a modal,
// and we're better off modifying it instead.
notifyOnError: false
}
data: uploadAjaxData
});
},

View File

@@ -1,10 +1,9 @@
define(
[
'jquery', 'underscore',
'jquery', 'underscore', 'edx-ui-toolkit/js/utils/html-utils', 'js/views/video/transcripts/utils',
'js/views/abstract_editor', 'js/models/uploads', 'js/views/uploads'
],
function($, _, AbstractEditor, FileUpload, UploadDialog) {
function($, _, HtmlUtils, TranscriptUtils, AbstractEditor, FileUpload, UploadDialog) {
'use strict';
var VideoUploadDialog = UploadDialog.extend({
@@ -31,13 +30,25 @@ function($, _, AbstractEditor, FileUpload, UploadDialog) {
initialize: function() {
var templateName = _.result(this, 'templateItemName'),
tpl = document.getElementById(templateName).text;
tpl = document.getElementById(templateName).text,
languageMap = {};
if (!tpl) {
console.error("Couldn't load template for item: " + templateName);
}
this.templateItem = _.template(tpl);
// Initialize language map. Keys in this map represent language codes present on
// server. Values will change if user changes the language from a dropdown.
// For example: Initially map will look like {'ar': 'ar', 'zh': 'zh'} and corresponding
// dropdowns will show language names `Arabic` and `Chinese`. If user changes `Chinese`
// to `Russian` then map will become {'ar': 'ar', 'zh': 'ru'}
_.each(this.model.getDisplayValue(), function(value, lang) {
languageMap[lang] = lang;
});
TranscriptUtils.Storage.set('languageMap', languageMap);
AbstractEditor.prototype.initialize.apply(this, arguments);
},
@@ -111,14 +122,16 @@ function($, _, AbstractEditor, FileUpload, UploadDialog) {
setValueInEditor: function(values) {
var self = this,
frag = document.createDocumentFragment(),
dropdown = self.getDropdown(values);
dropdown = self.getDropdown(values),
languageMap = TranscriptUtils.Storage.get('languageMap');
_.each(values, function(value, key) {
_.each(values, function(value, newLang) {
var html = $(self.templateItem({
lang: key,
newLang: newLang,
originalLang: _.findKey(languageMap, function(lang){ return lang === newLang}) || '',
value: value,
url: self.model.get('urlRoot') + '/' + key
})).prepend(dropdown.clone().val(key))[0];
url: self.model.get('urlRoot')
})).prepend(dropdown.clone().val(newLang))[0];
frag.appendChild(html);
});
@@ -130,18 +143,25 @@ function($, _, AbstractEditor, FileUpload, UploadDialog) {
event.preventDefault();
// We don't call updateModel here since it's bound to the
// change event
var dict = $.extend(true, {}, this.model.get('value'));
dict[''] = '';
this.setValueInEditor(dict);
this.setValueInEditor(this.getAllLanguageDropdownElementsData(true));
this.$el.find('.create-setting').addClass('is-disabled').attr('aria-disabled', true);
},
removeEntry: function(event) {
event.preventDefault();
var $currentListItemEl = $(event.currentTarget).parent(),
originalLang = $currentListItemEl.data('original-lang'),
selectedLang = $currentListItemEl.find('select option:selected').val(),
languageMap = TranscriptUtils.Storage.get('languageMap');
// re-render dropdowns
this.setValueInEditor(this.getAllLanguageDropdownElementsData(false, selectedLang));
// remvove the `originalLang` from `languageMap`
if (originalLang) {
TranscriptUtils.Storage.set('languageMap', _.omit(languageMap, originalLang));
}
var entry = $(event.currentTarget).data('lang');
this.setValueInEditor(_.omit(this.model.get('value'), entry));
this.updateModel();
this.$el.find('.create-setting').removeClass('is-disabled').attr('aria-disabled', false);
},
@@ -150,26 +170,58 @@ function($, _, AbstractEditor, FileUpload, UploadDialog) {
var self = this,
$target = $(event.currentTarget),
lang = $target.data('lang'),
model = new FileUpload({
title: gettext('Upload translation'),
fileFormats: ['srt']
}),
view = new VideoUploadDialog({
model: model,
url: self.model.get('urlRoot') + '/' + lang,
parentElement: $target.closest('.xblock-editor'),
onSuccess: function(response) {
if (!response.filename) { return; }
$listItem = $target.parents('li.list-settings-item'),
originalLang = $listItem.data('original-lang'),
newLang = $listItem.find(':selected').val(),
edxVideoIdField = TranscriptUtils.getField(self.model.collection, 'edx_video_id'),
fileUploadModel,
uploadData,
videoUploadDialog;
var dict = $.extend(true, {}, self.model.get('value'));
// That's the case when an author is
// uploading a new transcript.
if (!originalLang) {
originalLang = newLang
}
dict[lang] = response.filename;
self.model.setValue(dict);
}
});
// Transcript data payload
uploadData = {
edx_video_id: edxVideoIdField.getValue(),
language_code: originalLang,
new_language_code: newLang
};
view.show();
fileUploadModel = new FileUpload({
title: gettext('Upload translation'),
fileFormats: ['srt']
});
videoUploadDialog = new VideoUploadDialog({
model: fileUploadModel,
url: this.model.get('urlRoot'),
parentElement: $target.closest('.xblock-editor'),
uploadData: uploadData,
onSuccess: function(response) {
var languageMap = TranscriptUtils.Storage.get('languageMap'),
newLangObject = {};
// new language entry to be added to languageMap
newLangObject[newLang] = newLang;
//Update edx-video-id
edxVideoIdField.setValue(response.edx_video_id);
// Update language map by omitting original lang and adding new lang
// if languageMap is empty then newLang will be added
// if an original lang is replaced with new lang then omit the original lang and the add new lang
languageMap = _.extend(_.omit(languageMap, originalLang), newLangObject);
TranscriptUtils.Storage.set('languageMap', languageMap);
// re-render the whole view
self.setValueInEditor(self.getAllLanguageDropdownElementsData());
}
});
videoUploadDialog.show();
},
enableAdd: function() {
@@ -184,10 +236,57 @@ function($, _, AbstractEditor, FileUpload, UploadDialog) {
},
onChangeHandler: function(event) {
this.showClearButton();
var $target = $(event.currentTarget),
$listItem = $target.parents('li.list-settings-item'),
originalLang = $listItem.data('original-lang'),
newLang = $listItem.find('select option:selected').val(),
languageMap = TranscriptUtils.Storage.get('languageMap');
// To protect against any new/unsaved language code in the map.
if (originalLang in languageMap) {
languageMap[originalLang] = newLang;
TranscriptUtils.Storage.set('languageMap', languageMap);
// an existing saved lang is changed, no need to re-render the whole view
return;
}
this.enableAdd();
this.updateModel();
this.setValueInEditor(this.getAllLanguageDropdownElementsData());
},
/**
* Constructs data extracted from each dropdown. This will be used to re-render the whole view.
*/
getAllLanguageDropdownElementsData: function(isNew, omittedLanguage) {
var data = {},
languageDropdownElements = this.$el.find('select'),
languageMap = TranscriptUtils.Storage.get('languageMap');
// data object will mirror the languageMap. `data` will contain lang to lang map as explained below
// {originalLang: originalLang}; original lang not changed
// {newLang: originalLang}; original lang changed to a new lang
// {selectedLang: ""}; new lang to be added, no entry in languageMap
_.each(languageDropdownElements, function(languageDropdown, index){
var language = $(languageDropdown).find(':selected').val();
data[language] = _.findKey(languageMap, function(lang){ return lang === language}) || "";
});
// This is needed to render an empty item that
// will be further used to upload a transcript.
if (isNew) {
data[""] = "";
}
// This Omits a language from the dropdown's data. It is
// needed when an item is going to be removed.
if (typeof(omittedLanguage) !== 'undefined') {
data = _.omit(data, omittedLanguage)
}
return data;
}
});
return Translations;

View File

@@ -1,5 +1,6 @@
<div class="wrapper-comp-setting metadata-video-translations">
<label class="label setting-label"><%= model.get('display_name')%></label>
<input class="upload-transcript-input is-hidden" type="file" name="file" accept=".srt"/>
<div class="wrapper-translations-settings">
<ol class="list-settings"></ol>
<a href="#" class="create-action create-setting">

View File

@@ -1,12 +1,13 @@
<li class="list-settings-item">
<a href="#" class="remove-action remove-setting" data-lang="<%= lang %>" data-value="<%= value %>"><span class="icon fa fa-times-circle" aria-hidden="true"></span><span class="sr"><%= gettext("Remove") %></span></a>
<li class="list-settings-item" data-original-lang="<%= originalLang %>">
<a href="#" class="remove-action remove-setting" data-value="<%= value %>"><span class="icon fa fa-times-circle" aria-hidden="true"></span><span class="sr"><%= gettext("Remove") %></span></a>
<input type="hidden" class="input" value="<%= value %>">
<div class="list-settings-buttons"><% if (lang) {
%><a href="#" class="upload-action upload-setting" data-lang="<%= lang %>" data-value="<%= value %>"><%= value ? gettext("Replace") : gettext("Upload") %>
<div class="list-settings-buttons"><% if (newLang) {
%><a href="#" class="upload-action upload-setting" data-value="<%= value %>"><%= value ? gettext("Replace") : gettext("Upload") %>
</a><%
} %><% if (value) {
%><a href="<%= url %>?filename=<%= value %>" class="download-action download-setting"><%= gettext("Download") %>
%><a href="<%= url %>?language_code=<%= originalLang %>" class="download-action download-setting"><%= gettext("Download") %>
</a><%
}
%><div>
%>
</div>
</li>

View File

@@ -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,12 +20,12 @@ 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
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,
TranscriptException,
@@ -34,9 +34,6 @@ from .transcripts_utils import (
get_transcript,
get_transcript_from_contentstore
)
from .transcripts_model_utils import (
is_val_transcript_feature_enabled_for_course
)
log = logging.getLogger(__name__)
@@ -381,6 +378,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 +441,74 @@ 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
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')
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 == 'GET':
language = request.GET.get('language_code')
if not language:
return Response(json={'error': _(u'Language is required.')}, status=400)
filename = request.GET.get('filename')
if not filename:
log.info("Invalid /translation request: no filename in request.GET")
return Response(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)
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']
else:
# Any other HTTP method is not allowed.
response = Response(status=404)
else: # unknown dispatch
log.debug("Dispatch is not allowed")

View File

@@ -513,8 +513,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 +529,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])

View File

@@ -295,67 +295,6 @@ class VideoEditorTest(CMSVideoBaseTest):
self.save_unit_settings()
self.assertFalse(self.video.is_captions_visible())
def test_translations_clearing_works_w_saving(self):
"""
Scenario: Translations clearing works correctly w/ 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 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
"""
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())
def test_cannot_upload_sjson_translation(self):
"""
Scenario: User cannot upload translations in sjson format

View File

@@ -21,7 +21,12 @@ 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 (
TranscriptException,
TranscriptsGenerationException,
Transcript,
edxval_api
)
from xmodule.x_module import STUDENT_VIEW
from .helpers import BaseTestXmodule
@@ -863,42 +868,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 +928,87 @@ 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)