Merge pull request #17827 from edx/ammar/EDUCATOR-2170-transcripts-field
transcripts rendering on advanced tab
This commit is contained in:
@@ -11,7 +11,8 @@ define(['backbone'], function(Backbone) {
|
||||
explicitly_set: null,
|
||||
default_value: null,
|
||||
options: null,
|
||||
type: null
|
||||
type: null,
|
||||
custom: false // Used only for non-metadata fields
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
@@ -24,6 +25,11 @@ define(['backbone'], function(Backbone) {
|
||||
* property has changed.
|
||||
*/
|
||||
isModified: function() {
|
||||
// A non-metadata field will handle itself
|
||||
if (this.get('custom') === true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.get('explicitly_set') && !this.original_explicitly_set) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
define(
|
||||
[
|
||||
'jquery', 'underscore',
|
||||
'js/views/abstract_editor', 'js/models/uploads', 'js/views/uploads'
|
||||
|
||||
'jquery', 'underscore', 'edx-ui-toolkit/js/utils/html-utils', 'js/views/video/transcripts/utils',
|
||||
'js/views/abstract_editor', 'common/js/components/utils/view_utils', 'js/models/uploads', 'js/views/uploads'
|
||||
],
|
||||
function($, _, AbstractEditor, FileUpload, UploadDialog) {
|
||||
function($, _, HtmlUtils, TranscriptUtils, AbstractEditor, ViewUtils, FileUpload, UploadDialog) {
|
||||
'use strict';
|
||||
|
||||
var VideoUploadDialog = UploadDialog.extend({
|
||||
@@ -19,7 +18,6 @@ function($, _, AbstractEditor, FileUpload, UploadDialog) {
|
||||
|
||||
var Translations = AbstractEditor.extend({
|
||||
events: {
|
||||
'click .setting-clear': 'clear',
|
||||
'click .create-setting': 'addEntry',
|
||||
'click .remove-setting': 'removeEntry',
|
||||
'click .upload-setting': 'upload',
|
||||
@@ -31,13 +29,24 @@ 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 +120,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,63 +141,166 @@ 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) {
|
||||
var self = this,
|
||||
$currentListItemEl = $(event.currentTarget).parent(),
|
||||
originalLang = $currentListItemEl.data('original-lang'),
|
||||
selectedLang = $currentListItemEl.find('select option:selected').val(),
|
||||
languageMap = TranscriptUtils.Storage.get('languageMap'),
|
||||
edxVideoIdField = TranscriptUtils.getField(self.model.collection, 'edx_video_id');
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
var entry = $(event.currentTarget).data('lang');
|
||||
this.setValueInEditor(_.omit(this.model.get('value'), entry));
|
||||
this.updateModel();
|
||||
/*
|
||||
There is a scenario when a user adds an empty video translation item and
|
||||
removes it. In such cases, omitting will have no harm on the model
|
||||
values or languages map.
|
||||
*/
|
||||
if (originalLang) {
|
||||
ViewUtils.confirmThenRunOperation(
|
||||
gettext('Are you sure you want to remove this transcript?'),
|
||||
gettext('If you remove this transcript, the transcript will not be available for this component.'),
|
||||
gettext('Remove Transcript'),
|
||||
function() {
|
||||
ViewUtils.runOperationShowingMessage(
|
||||
gettext('Removing'),
|
||||
function() {
|
||||
return $.ajax({
|
||||
url: self.model.get('urlRoot'),
|
||||
type: 'DELETE',
|
||||
data: JSON.stringify({lang: originalLang, edx_video_id: edxVideoIdField.getValue()})
|
||||
}).done(function() {
|
||||
self.setValueInEditor(self.getAllLanguageDropdownElementsData(false, selectedLang));
|
||||
TranscriptUtils.Storage.set('languageMap', _.omit(languageMap, originalLang));
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.setValueInEditor(this.getAllLanguageDropdownElementsData(false, selectedLang));
|
||||
}
|
||||
|
||||
this.$el.find('.create-setting').removeClass('is-disabled').attr('aria-disabled', false);
|
||||
},
|
||||
|
||||
upload: function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
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'));
|
||||
event.preventDefault();
|
||||
|
||||
dict[lang] = response.filename;
|
||||
self.model.setValue(dict);
|
||||
}
|
||||
});
|
||||
// That's the case when an author is
|
||||
// uploading a new transcript.
|
||||
if (!originalLang) {
|
||||
originalLang = newLang;
|
||||
}
|
||||
|
||||
view.show();
|
||||
// Transcript data payload
|
||||
uploadData = {
|
||||
edx_video_id: edxVideoIdField.getValue(),
|
||||
language_code: originalLang,
|
||||
new_language_code: newLang
|
||||
};
|
||||
|
||||
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() {
|
||||
this.$el.find('.create-setting').removeClass('is-disabled').attr('aria-disabled', false);
|
||||
},
|
||||
|
||||
clear: function() {
|
||||
AbstractEditor.prototype.clear.apply(this, arguments);
|
||||
if (_.isNull(this.model.getValue())) {
|
||||
this.$el.find('.create-setting').removeClass('is-disabled').attr('aria-disabled', false);
|
||||
onChangeHandler: function(event) {
|
||||
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.setValueInEditor(this.getAllLanguageDropdownElementsData());
|
||||
},
|
||||
|
||||
onChangeHandler: function(event) {
|
||||
this.showClearButton();
|
||||
this.enableAdd();
|
||||
this.updateModel();
|
||||
/**
|
||||
* 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) {
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
<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">
|
||||
<span class="icon fa fa-plus" aria-hidden="true"></span><%= gettext("Add") %> <span class="sr"><%= model.get('display_name')%></span>
|
||||
</a>
|
||||
</div>
|
||||
<button class="action setting-clear inactive" type="button" name="setting-clear" value="<%= gettext("Clear") %>" data-tooltip="<%= gettext("Clear") %>">
|
||||
<span class="icon fa fa-undo" aria-hidden="true"></span>
|
||||
<span class="sr">"<%= gettext("Clear Value") %>"</span>
|
||||
</button>
|
||||
</div>
|
||||
<span class="tip setting-help"><%= model.get('help') %></span>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user