Commit 55621365 by Qubad786 Committed by Mushtaq Ali

Add waffle switch to toggle video thumbnail feature.

parent eabfba48
...@@ -6,6 +6,7 @@ import csv ...@@ -6,6 +6,7 @@ import csv
import json import json
import re import re
from datetime import datetime from datetime import datetime
from functools import wraps
from StringIO import StringIO from StringIO import StringIO
import dateutil.parser import dateutil.parser
...@@ -21,11 +22,10 @@ from contentstore.models import VideoUploadConfig ...@@ -21,11 +22,10 @@ from contentstore.models import VideoUploadConfig
from contentstore.tests.utils import CourseTestCase from contentstore.tests.utils import CourseTestCase
from contentstore.utils import reverse_course_url from contentstore.utils import reverse_course_url
from contentstore.views.videos import ( from contentstore.views.videos import (
KEY_EXPIRATION_IN_SECONDS,
StatusDisplayStrings,
convert_video_status,
_get_default_video_image_url, _get_default_video_image_url,
validate_video_image validate_video_image,
VIDEO_IMAGE_UPLOAD_ENABLED,
WAFFLE_SWITCHES,
) )
from contentstore.views.videos import KEY_EXPIRATION_IN_SECONDS, StatusDisplayStrings, convert_video_status from contentstore.views.videos import KEY_EXPIRATION_IN_SECONDS, StatusDisplayStrings, convert_video_status
from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.factories import CourseFactory
...@@ -33,6 +33,24 @@ from xmodule.modulestore.tests.factories import CourseFactory ...@@ -33,6 +33,24 @@ from xmodule.modulestore.tests.factories import CourseFactory
from openedx.core.djangoapps.profile_images.tests.helpers import make_image_file from openedx.core.djangoapps.profile_images.tests.helpers import make_image_file
def override_switch(switch, active):
"""
Overrides the given waffle switch to `active` boolean.
Arguments:
switch(str): switch name
active(bool): A boolean representing (to be overridden) value
"""
def decorate(function):
@wraps(function)
def inner(*args, **kwargs):
with WAFFLE_SWITCHES.override(switch, active=active):
function(*args, **kwargs)
return inner
return decorate
class VideoUploadTestBase(object): class VideoUploadTestBase(object):
""" """
Test cases for the video upload feature Test cases for the video upload feature
...@@ -576,6 +594,16 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase): ...@@ -576,6 +594,16 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase):
self.assertIn('error', response) self.assertIn('error', response)
self.assertEqual(response['error'], error_message) self.assertEqual(response['error'], error_message)
@override_switch(VIDEO_IMAGE_UPLOAD_ENABLED, False)
def test_video_image_upload_disabled(self):
"""
Tests the video image upload when the feature is disabled.
"""
video_image_upload_url = self.get_url_for_course_key(self.course.id, {'edx_video_id': 'test_vid_id'})
response = self.client.post(video_image_upload_url, {'file': 'dummy_file'}, format='multipart')
self.assertEqual(response.status_code, 404)
@override_switch(VIDEO_IMAGE_UPLOAD_ENABLED, True)
def test_video_image(self): def test_video_image(self):
""" """
Test video image is saved. Test video image is saved.
...@@ -597,6 +625,7 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase): ...@@ -597,6 +625,7 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase):
self.assertNotEqual(image_url1, image_url2) self.assertNotEqual(image_url1, image_url2)
@override_switch(VIDEO_IMAGE_UPLOAD_ENABLED, True)
def test_video_image_no_file(self): def test_video_image_no_file(self):
""" """
Test that an error error message is returned if upload request is incorrect. Test that an error error message is returned if upload request is incorrect.
...@@ -625,6 +654,7 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase): ...@@ -625,6 +654,7 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase):
error = validate_video_image(image_file) error = validate_video_image(image_file)
self.assertEquals(error, 'This image file is corrupted.') self.assertEquals(error, 'This image file is corrupted.')
@override_switch(VIDEO_IMAGE_UPLOAD_ENABLED, True)
def test_no_video_image(self): def test_no_video_image(self):
""" """
Test image url is set to None if no video image. Test image url is set to None if no video image.
...@@ -800,6 +830,7 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase): ...@@ -800,6 +830,7 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase):
) )
) )
@ddt.unpack @ddt.unpack
@override_switch(VIDEO_IMAGE_UPLOAD_ENABLED, True)
def test_video_image_validation_message(self, image_data, error_message): def test_video_image_validation_message(self, image_data, error_message):
""" """
Test video image validation gives proper error message. Test video image validation gives proper error message.
......
...@@ -2,10 +2,7 @@ ...@@ -2,10 +2,7 @@
Views related to the video upload feature Views related to the video upload feature
""" """
from contextlib import closing from contextlib import closing
from datetime import datetime, timedelta
import logging
from boto import s3
import csv import csv
import logging import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
...@@ -31,6 +28,7 @@ from edxval.api import ( ...@@ -31,6 +28,7 @@ from edxval.api import (
update_video_image update_video_image
) )
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace
from contentstore.models import VideoUploadConfig from contentstore.models import VideoUploadConfig
from contentstore.utils import reverse_course_url from contentstore.utils import reverse_course_url
...@@ -44,6 +42,12 @@ __all__ = ['videos_handler', 'video_encodings_download', 'video_images_handler'] ...@@ -44,6 +42,12 @@ __all__ = ['videos_handler', 'video_encodings_download', 'video_images_handler']
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
# Waffle switches namespace for videos
WAFFLE_NAMESPACE = 'videos'
WAFFLE_SWITCHES = WaffleSwitchNamespace(name=WAFFLE_NAMESPACE)
# Waffle switch for enabling/disabling video image upload feature
VIDEO_IMAGE_UPLOAD_ENABLED = 'video_image_upload_enabled'
# Default expiration, in seconds, of one-time URLs used for uploading videos. # Default expiration, in seconds, of one-time URLs used for uploading videos.
KEY_EXPIRATION_IN_SECONDS = 86400 KEY_EXPIRATION_IN_SECONDS = 86400
...@@ -210,6 +214,11 @@ def validate_video_image(image_file): ...@@ -210,6 +214,11 @@ def validate_video_image(image_file):
@login_required @login_required
@require_POST @require_POST
def video_images_handler(request, course_key_string, edx_video_id=None): def video_images_handler(request, course_key_string, edx_video_id=None):
# respond with a 404 if image upload is not enabled.
if not WAFFLE_SWITCHES.is_enabled(VIDEO_IMAGE_UPLOAD_ENABLED):
return HttpResponseNotFound()
if 'file' not in request.FILES: if 'file' not in request.FILES:
return JsonResponse({'error': _(u'No file provided for video image')}, status=400) return JsonResponse({'error': _(u'No file provided for video image')}, status=400)
...@@ -428,6 +437,7 @@ def videos_index_html(course): ...@@ -428,6 +437,7 @@ def videos_index_html(course):
'video_supported_file_formats': VIDEO_SUPPORTED_FILE_FORMATS.keys(), 'video_supported_file_formats': VIDEO_SUPPORTED_FILE_FORMATS.keys(),
'video_upload_max_file_size': VIDEO_UPLOAD_MAX_FILE_SIZE_GB, 'video_upload_max_file_size': VIDEO_UPLOAD_MAX_FILE_SIZE_GB,
'video_image_settings': { 'video_image_settings': {
'video_image_upload_enabled': WAFFLE_SWITCHES.is_enabled(VIDEO_IMAGE_UPLOAD_ENABLED),
'max_size': settings.VIDEO_IMAGE_SETTINGS['VIDEO_IMAGE_MAX_BYTES'], 'max_size': settings.VIDEO_IMAGE_SETTINGS['VIDEO_IMAGE_MAX_BYTES'],
'min_size': settings.VIDEO_IMAGE_SETTINGS['VIDEO_IMAGE_MIN_BYTES'], 'min_size': settings.VIDEO_IMAGE_SETTINGS['VIDEO_IMAGE_MIN_BYTES'],
'max_width': settings.VIDEO_IMAGE_MAX_WIDTH, 'max_width': settings.VIDEO_IMAGE_MAX_WIDTH,
......
...@@ -29,11 +29,12 @@ define( ...@@ -29,11 +29,12 @@ define(
/** /**
* Creates a list of video records. * Creates a list of video records.
* *
* @param {Boolean} videoImageUploadEnabled Boolean indicating if the feature is enabled.
* @param {Object} modelData Model data for video records. * @param {Object} modelData Model data for video records.
* @param {Integer} numVideos Number of video elements to create. * @param {Integer} numVideos Number of video elements to create.
* @param {Integer} videoViewIndex Index of video on which videoThumbnailView would be based. * @param {Integer} videoViewIndex Index of video on which videoThumbnailView would be based.
*/ */
createVideoListView = function(modelData, numVideos, videoViewIndex) { createVideoListView = function(videoImageUploadEnabled, modelData, numVideos, videoViewIndex) {
var modelData = modelData || {}, // eslint-disable-line no-redeclare var modelData = modelData || {}, // eslint-disable-line no-redeclare
numVideos = numVideos || 1, // eslint-disable-line no-redeclare numVideos = numVideos || 1, // eslint-disable-line no-redeclare
videoViewIndex = videoViewIndex || 0, // eslint-disable-line no-redeclare, videoViewIndex = videoViewIndex || 0, // eslint-disable-line no-redeclare,
...@@ -58,12 +59,17 @@ define( ...@@ -58,12 +59,17 @@ define(
min_size: VIDEO_IMAGE_MIN_BYTES, min_size: VIDEO_IMAGE_MIN_BYTES,
max_width: VIDEO_IMAGE_MAX_WIDTH, max_width: VIDEO_IMAGE_MAX_WIDTH,
max_height: VIDEO_IMAGE_MAX_HEIGHT, max_height: VIDEO_IMAGE_MAX_HEIGHT,
supported_file_formats: VIDEO_IMAGE_SUPPORTED_FILE_FORMATS supported_file_formats: VIDEO_IMAGE_SUPPORTED_FILE_FORMATS,
video_image_upload_enabled: videoImageUploadEnabled
} }
}); });
$videoListEl = videoListView.render().$el; $videoListEl = videoListView.render().$el;
videoThumbnailView = videoListView.itemViews[videoViewIndex].videoThumbnailView;
$videoThumbnailEl = videoThumbnailView.render().$el; if (videoImageUploadEnabled) {
videoThumbnailView = videoListView.itemViews[videoViewIndex].videoThumbnailView;
$videoThumbnailEl = videoThumbnailView.render().$el;
}
return videoListView; return videoListView;
}; };
...@@ -113,7 +119,12 @@ define( ...@@ -113,7 +119,12 @@ define(
setFixtures('<div id="page-prompt"></div><div id="page-notification"></div>'); setFixtures('<div id="page-prompt"></div><div id="page-notification"></div>');
TemplateHelpers.installTemplate('video-thumbnail'); TemplateHelpers.installTemplate('video-thumbnail');
TemplateHelpers.installTemplate('previous-video-upload-list'); TemplateHelpers.installTemplate('previous-video-upload-list');
createVideoListView(); createVideoListView(true);
});
it('Verifies that the ThumbnailView is not initialized on disabling the feature', function() {
createVideoListView(false);
expect(videoListView.itemViews[0].videoThumbnailView).toEqual(undefined);
}); });
it('renders as expected', function() { it('renders as expected', function() {
...@@ -122,7 +133,7 @@ define( ...@@ -122,7 +133,7 @@ define(
}); });
it('does not show duration if not available', function() { it('does not show duration if not available', function() {
createVideoListView({duration: 0}); createVideoListView(true, {duration: 0});
expect($videoThumbnailEl.find('.thumbnail-wrapper .video-duration')).not.toExist(); expect($videoThumbnailEl.find('.thumbnail-wrapper .video-duration')).not.toExist();
}); });
......
...@@ -19,16 +19,21 @@ define( ...@@ -19,16 +19,21 @@ define(
initialize: function(options) { initialize: function(options) {
this.template = HtmlUtils.template(previousVideoUploadTemplate); this.template = HtmlUtils.template(previousVideoUploadTemplate);
this.videoHandlerUrl = options.videoHandlerUrl; this.videoHandlerUrl = options.videoHandlerUrl;
this.videoThumbnailView = new VideoThumbnailView({ this.videoImageUploadEnabled = options.videoImageSettings.video_image_upload_enabled;
model: this.model,
imageUploadURL: options.videoImageUploadURL, if (this.videoImageUploadEnabled) {
defaultVideoImageURL: options.defaultVideoImageURL, this.videoThumbnailView = new VideoThumbnailView({
videoImageSettings: options.videoImageSettings model: this.model,
}); imageUploadURL: options.videoImageUploadURL,
defaultVideoImageURL: options.defaultVideoImageURL,
videoImageSettings: options.videoImageSettings
});
}
}, },
render: function() { render: function() {
var renderedAttributes = { var renderedAttributes = {
videoImageUploadEnabled: this.videoImageUploadEnabled,
created: DateUtils.renderDate(this.model.get('created')), created: DateUtils.renderDate(this.model.get('created')),
status: this.model.get('status') status: this.model.get('status')
}; };
...@@ -38,7 +43,10 @@ define( ...@@ -38,7 +43,10 @@ define(
_.extend({}, this.model.attributes, renderedAttributes) _.extend({}, this.model.attributes, renderedAttributes)
) )
); );
this.videoThumbnailView.setElement(this.$('.thumbnail-col')).render();
if (this.videoImageUploadEnabled) {
this.videoThumbnailView.setElement(this.$('.thumbnail-col')).render();
}
return this; return this;
}, },
......
...@@ -9,6 +9,7 @@ define( ...@@ -9,6 +9,7 @@ define(
initialize: function(options) { initialize: function(options) {
this.template = this.loadTemplate('previous-video-upload-list'); this.template = this.loadTemplate('previous-video-upload-list');
this.encodingsDownloadUrl = options.encodingsDownloadUrl; this.encodingsDownloadUrl = options.encodingsDownloadUrl;
this.videoImageUploadEnabled = options.videoImageSettings.video_image_upload_enabled;
this.itemViews = this.collection.map(function(model) { this.itemViews = this.collection.map(function(model) {
return new PreviousVideoUploadView({ return new PreviousVideoUploadView({
videoImageUploadURL: options.videoImageUploadURL, videoImageUploadURL: options.videoImageUploadURL,
...@@ -23,7 +24,10 @@ define( ...@@ -23,7 +24,10 @@ define(
render: function() { render: function() {
var $el = this.$el, var $el = this.$el,
$tabBody; $tabBody;
$el.html(this.template({encodingsDownloadUrl: this.encodingsDownloadUrl})); $el.html(this.template({
encodingsDownloadUrl: this.encodingsDownloadUrl,
videoImageUploadEnabled: this.videoImageUploadEnabled
}));
$tabBody = $el.find('.js-table-body'); $tabBody = $el.find('.js-table-body');
_.each(this.itemViews, function(view) { _.each(this.itemViews, function(view) {
$tabBody.append(view.render().$el); $tabBody.append(view.render().$el);
......
...@@ -8,7 +8,9 @@ ...@@ -8,7 +8,9 @@
<div class="assets-table video-table"> <div class="assets-table video-table">
<div class="js-table-head"> <div class="js-table-head">
<div class="video-row"> <div class="video-row">
<% if (videoImageUploadEnabled) { %>
<div class="video-head-col video-col thumbnail-col"><%- gettext("Thumbnail") %></div> <div class="video-head-col video-col thumbnail-col"><%- gettext("Thumbnail") %></div>
<% } %>
<div class="video-head-col video-col name-col"><%- gettext("Name") %></div> <div class="video-head-col video-col name-col"><%- gettext("Name") %></div>
<div class="video-head-col video-col date-col"><%- gettext("Date Added") %></div> <div class="video-head-col video-col date-col"><%- gettext("Date Added") %></div>
<div class="video-head-col video-col video-id-col"><%- gettext("Video ID") %></div> <div class="video-head-col video-col video-id-col"><%- gettext("Video ID") %></div>
......
<div class="video-row-container"> <div class="video-row-container">
<% if (videoImageUploadEnabled) { %>
<div class="video-col thumbnail-col"></div> <div class="video-col thumbnail-col"></div>
<% } %>
<div class="video-col name-col"><%- client_video_id %></div> <div class="video-col name-col"><%- client_video_id %></div>
<div class="video-col date-col"><%- created %></div> <div class="video-col date-col"><%- created %></div>
<div class="video-col video-id-col"><%- edx_video_id %></div> <div class="video-col video-id-col"><%- edx_video_id %></div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment