From 26a1f4506f9b952c886bca56e9be212afc7c457d Mon Sep 17 00:00:00 2001 From: muhammad-ammar Date: Thu, 29 Mar 2018 14:38:18 +0500 Subject: [PATCH 1/3] transcripts rendering on advanced tab EDUCATOR-2170 --- cms/static/js/models/metadata.js | 8 +++++++- .../xmodule/video_module/video_module.py | 20 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/cms/static/js/models/metadata.js b/cms/static/js/models/metadata.js index 68aef82b27..923c51647b 100644 --- a/cms/static/js/models/metadata.js +++ b/cms/static/js/models/metadata.js @@ -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; } 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', From cadd4378c71e587184856a13df5360d08d7d43a7 Mon Sep 17 00:00:00 2001 From: Qubad786 Date: Sun, 1 Apr 2018 16:02:17 +0500 Subject: [PATCH 2/3] updload/download/replace transcripts EDUCATOR-2167 EDUCATOR-2168 EDUCATOR-2169 --- cms/static/js/views/uploads.js | 17 +- .../js/views/video/translations_editor.js | 167 ++++++++++++++---- .../metadata-translations-entry.underscore | 1 + .../metadata-translations-item.underscore | 13 +- .../xmodule/video_module/video_handlers.py | 130 ++++++++++---- .../acceptance/pages/studio/video/video.py | 6 +- .../tests/video/test_studio_video_editor.py | 61 ------- .../courseware/tests/test_video_handlers.py | 156 ++++++++++------ 8 files changed, 356 insertions(+), 195 deletions(-) diff --git a/cms/static/js/views/uploads.js b/cms/static/js/views/uploads.js index 8a24c7984f..1b7142b6b0 100644 --- a/cms/static/js/views/uploads.js +++ b/cms/static/js/views/uploads.js @@ -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 }); }, diff --git a/cms/static/js/views/video/translations_editor.js b/cms/static/js/views/video/translations_editor.js index 2e776656f4..a68dcee05a 100644 --- a/cms/static/js/views/video/translations_editor.js +++ b/cms/static/js/views/video/translations_editor.js @@ -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; diff --git a/cms/templates/js/video/metadata-translations-entry.underscore b/cms/templates/js/video/metadata-translations-entry.underscore index a7a5453f73..421513dd8f 100644 --- a/cms/templates/js/video/metadata-translations-entry.underscore +++ b/cms/templates/js/video/metadata-translations-entry.underscore @@ -1,5 +1,6 @@ <%= model.get('help') %> diff --git a/common/lib/xmodule/xmodule/video_module/video_handlers.py b/common/lib/xmodule/xmodule/video_module/video_handlers.py index 1e7e193353..19b17f2dd9 100644 --- a/common/lib/xmodule/xmodule/video_module/video_handlers.py +++ b/common/lib/xmodule/xmodule/video_module/video_handlers.py @@ -20,19 +20,21 @@ 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 edxval.api import create_or_update_video_transcript, create_external_video, delete_video_transcript from .transcripts_utils import ( clean_video_id, get_or_create_sjson, generate_sjson_for_all_speeds, - get_video_transcript_content, + save_to_store, subs_filename, Transcript, TranscriptException, TranscriptsGenerationException, youtube_speed_dict, get_transcript, - get_transcript_from_contentstore + get_transcript_from_contentstore, + remove_subs_from_store, + get_html5_ids ) log = logging.getLogger(__name__) @@ -488,6 +490,36 @@ class VideoStudioViewHandlers(object): }, 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) + + 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') diff --git a/common/test/acceptance/pages/studio/video/video.py b/common/test/acceptance/pages/studio/video/video.py index f5bb6468ce..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 @@ -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 5a09b64c3f..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,9 +296,28 @@ 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_entry_remove_works(self): + """ + 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 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.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): """ Scenario: User cannot upload translations in sjson format @@ -394,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 c941e4fc08..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 @@ -22,13 +23,14 @@ 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, - edxval_api + 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 @@ -1011,6 +1013,138 @@ class TestStudioTranscriptTranslationPostDispatch(TestVideo): ) +@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) class TestGetTranscript(TestVideo): """