Save or update course video image - TNL-6762
This commit is contained in:
committed by
Mushtaq Ali
parent
7b91095398
commit
dca12d65c2
@@ -1975,7 +1975,7 @@ class RerunCourseTest(ContentStoreTestCase):
|
||||
create_video(
|
||||
dict(
|
||||
edx_video_id="tree-hugger",
|
||||
courses=[source_course.id],
|
||||
courses=[unicode(source_course.id)],
|
||||
status='test',
|
||||
duration=2,
|
||||
encoded_videos=[]
|
||||
|
||||
@@ -19,11 +19,19 @@ from mock import Mock, patch
|
||||
from contentstore.models import VideoUploadConfig
|
||||
from contentstore.tests.utils import CourseTestCase
|
||||
from contentstore.utils import reverse_course_url
|
||||
from contentstore.views.videos import (
|
||||
KEY_EXPIRATION_IN_SECONDS,
|
||||
StatusDisplayStrings,
|
||||
convert_video_status,
|
||||
_get_default_video_image_url
|
||||
)
|
||||
from contentstore.views.videos import KEY_EXPIRATION_IN_SECONDS, StatusDisplayStrings, convert_video_status
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
from openedx.core.djangoapps.profile_images.tests.helpers import make_image_file
|
||||
|
||||
class VideoUploadTestMixin(object):
|
||||
|
||||
class VideoUploadTestBase(object):
|
||||
"""
|
||||
Test cases for the video upload feature
|
||||
"""
|
||||
@@ -32,7 +40,7 @@ class VideoUploadTestMixin(object):
|
||||
return reverse_course_url(self.VIEW_NAME, course_key, kwargs)
|
||||
|
||||
def setUp(self):
|
||||
super(VideoUploadTestMixin, self).setUp()
|
||||
super(VideoUploadTestBase, self).setUp()
|
||||
self.url = self.get_url_for_course_key(self.course.id)
|
||||
self.test_token = "test_token"
|
||||
self.course.video_upload_pipeline = {
|
||||
@@ -131,6 +139,11 @@ class VideoUploadTestMixin(object):
|
||||
if video["edx_video_id"] == edx_video_id
|
||||
)
|
||||
|
||||
|
||||
class VideoUploadTestMixin(VideoUploadTestBase):
|
||||
"""
|
||||
Test cases for the video upload feature
|
||||
"""
|
||||
def test_anon_user(self):
|
||||
self.client.logout()
|
||||
response = self.client.get(self.url)
|
||||
@@ -171,25 +184,25 @@ class VideoUploadTestMixin(object):
|
||||
class VideosHandlerTestCase(VideoUploadTestMixin, CourseTestCase):
|
||||
"""Test cases for the main video upload endpoint"""
|
||||
|
||||
VIEW_NAME = "videos_handler"
|
||||
VIEW_NAME = 'videos_handler'
|
||||
|
||||
def test_get_json(self):
|
||||
response = self.client.get_json(self.url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response_videos = json.loads(response.content)["videos"]
|
||||
response_videos = json.loads(response.content)['videos']
|
||||
self.assertEqual(len(response_videos), len(self.previous_uploads))
|
||||
for i, response_video in enumerate(response_videos):
|
||||
# Videos should be returned by creation date descending
|
||||
original_video = self.previous_uploads[-(i + 1)]
|
||||
self.assertEqual(
|
||||
set(response_video.keys()),
|
||||
set(["edx_video_id", "client_video_id", "created", "duration", "status"])
|
||||
set(['edx_video_id', 'client_video_id', 'created', 'duration', 'status', 'course_video_image_url'])
|
||||
)
|
||||
dateutil.parser.parse(response_video["created"])
|
||||
for field in ["edx_video_id", "client_video_id", "duration"]:
|
||||
dateutil.parser.parse(response_video['created'])
|
||||
for field in ['edx_video_id', 'client_video_id', 'duration']:
|
||||
self.assertEqual(response_video[field], original_video[field])
|
||||
self.assertEqual(
|
||||
response_video["status"],
|
||||
response_video['status'],
|
||||
convert_video_status(original_video)
|
||||
)
|
||||
|
||||
@@ -313,26 +326,26 @@ class VideosHandlerTestCase(VideoUploadTestMixin, CourseTestCase):
|
||||
response = json.loads(response.content)
|
||||
self.assertEqual(response['error'], 'The file name for %s must contain only ASCII characters.' % file_name)
|
||||
|
||||
@override_settings(AWS_ACCESS_KEY_ID="test_key_id", AWS_SECRET_ACCESS_KEY="test_secret")
|
||||
@patch("boto.s3.key.Key")
|
||||
@patch("boto.s3.connection.S3Connection")
|
||||
@override_settings(AWS_ACCESS_KEY_ID='test_key_id', AWS_SECRET_ACCESS_KEY='test_secret')
|
||||
@patch('boto.s3.key.Key')
|
||||
@patch('boto.s3.connection.S3Connection')
|
||||
def test_post_success(self, mock_conn, mock_key):
|
||||
files = [
|
||||
{
|
||||
"file_name": "first.mp4",
|
||||
"content_type": "video/mp4",
|
||||
'file_name': 'first.mp4',
|
||||
'content_type': 'video/mp4',
|
||||
},
|
||||
{
|
||||
"file_name": "second.mp4",
|
||||
"content_type": "video/mp4",
|
||||
'file_name': 'second.mp4',
|
||||
'content_type': 'video/mp4',
|
||||
},
|
||||
{
|
||||
"file_name": "third.mov",
|
||||
"content_type": "video/quicktime",
|
||||
'file_name': 'third.mov',
|
||||
'content_type': 'video/quicktime',
|
||||
},
|
||||
{
|
||||
"file_name": "fourth.mp4",
|
||||
"content_type": "video/mp4",
|
||||
'file_name': 'fourth.mp4',
|
||||
'content_type': 'video/mp4',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -341,7 +354,7 @@ class VideosHandlerTestCase(VideoUploadTestMixin, CourseTestCase):
|
||||
mock_key_instances = [
|
||||
Mock(
|
||||
generate_url=Mock(
|
||||
return_value="http://example.com/url_{}".format(file_info["file_name"])
|
||||
return_value='http://example.com/url_{}'.format(file_info['file_name'])
|
||||
)
|
||||
)
|
||||
for file_info in files
|
||||
@@ -351,14 +364,14 @@ class VideosHandlerTestCase(VideoUploadTestMixin, CourseTestCase):
|
||||
|
||||
response = self.client.post(
|
||||
self.url,
|
||||
json.dumps({"files": files}),
|
||||
content_type="application/json"
|
||||
json.dumps({'files': files}),
|
||||
content_type='application/json'
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response_obj = json.loads(response.content)
|
||||
|
||||
mock_conn.assert_called_once_with(settings.AWS_ACCESS_KEY_ID, settings.AWS_SECRET_ACCESS_KEY)
|
||||
self.assertEqual(len(response_obj["files"]), len(files))
|
||||
self.assertEqual(len(response_obj['files']), len(files))
|
||||
self.assertEqual(mock_key.call_count, len(files))
|
||||
for i, file_info in enumerate(files):
|
||||
# Ensure Key was set up correctly and extract id
|
||||
@@ -366,8 +379,8 @@ class VideosHandlerTestCase(VideoUploadTestMixin, CourseTestCase):
|
||||
self.assertEqual(key_call_args[0], bucket)
|
||||
path_match = re.match(
|
||||
(
|
||||
settings.VIDEO_UPLOAD_PIPELINE["ROOT_PATH"] +
|
||||
"/([a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12})$"
|
||||
settings.VIDEO_UPLOAD_PIPELINE['ROOT_PATH'] +
|
||||
'/([a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12})$'
|
||||
),
|
||||
key_call_args[1]
|
||||
)
|
||||
@@ -375,32 +388,32 @@ class VideosHandlerTestCase(VideoUploadTestMixin, CourseTestCase):
|
||||
video_id = path_match.group(1)
|
||||
mock_key_instance = mock_key_instances[i]
|
||||
mock_key_instance.set_metadata.assert_any_call(
|
||||
"course_video_upload_token",
|
||||
'course_video_upload_token',
|
||||
self.test_token
|
||||
)
|
||||
mock_key_instance.set_metadata.assert_any_call(
|
||||
"client_video_id",
|
||||
file_info["file_name"]
|
||||
'client_video_id',
|
||||
file_info['file_name']
|
||||
)
|
||||
mock_key_instance.set_metadata.assert_any_call("course_key", unicode(self.course.id))
|
||||
mock_key_instance.set_metadata.assert_any_call('course_key', unicode(self.course.id))
|
||||
mock_key_instance.generate_url.assert_called_once_with(
|
||||
KEY_EXPIRATION_IN_SECONDS,
|
||||
"PUT",
|
||||
headers={"Content-Type": file_info["content_type"]}
|
||||
'PUT',
|
||||
headers={'Content-Type': file_info['content_type']}
|
||||
)
|
||||
|
||||
# Ensure VAL was updated
|
||||
val_info = get_video_info(video_id)
|
||||
self.assertEqual(val_info["status"], "upload")
|
||||
self.assertEqual(val_info["client_video_id"], file_info["file_name"])
|
||||
self.assertEqual(val_info["status"], "upload")
|
||||
self.assertEqual(val_info["duration"], 0)
|
||||
self.assertEqual(val_info["courses"], [unicode(self.course.id)])
|
||||
self.assertEqual(val_info['status'], 'upload')
|
||||
self.assertEqual(val_info['client_video_id'], file_info['file_name'])
|
||||
self.assertEqual(val_info['status'], 'upload')
|
||||
self.assertEqual(val_info['duration'], 0)
|
||||
self.assertEqual(val_info['courses'], [{unicode(self.course.id): None}])
|
||||
|
||||
# Ensure response is correct
|
||||
response_file = response_obj["files"][i]
|
||||
self.assertEqual(response_file["file_name"], file_info["file_name"])
|
||||
self.assertEqual(response_file["upload_url"], mock_key_instance.generate_url())
|
||||
response_file = response_obj['files'][i]
|
||||
self.assertEqual(response_file['file_name'], file_info['file_name'])
|
||||
self.assertEqual(response_file['upload_url'], mock_key_instance.generate_url())
|
||||
|
||||
def _assert_video_removal(self, url, edx_video_id, deleted_videos):
|
||||
"""
|
||||
@@ -518,6 +531,84 @@ class VideosHandlerTestCase(VideoUploadTestMixin, CourseTestCase):
|
||||
self.assert_video_status(url, edx_video_id, 'Failed')
|
||||
|
||||
|
||||
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_VIDEO_UPLOAD_PIPELINE': True})
|
||||
@override_settings(VIDEO_UPLOAD_PIPELINE={'BUCKET': 'test_bucket', 'ROOT_PATH': 'test_root'})
|
||||
class VideoImageTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
"""
|
||||
Tests for video image.
|
||||
"""
|
||||
|
||||
VIEW_NAME = "video_images_handler"
|
||||
|
||||
def verify_image_upload_reponse(self, course_id, edx_video_id, upload_response):
|
||||
"""
|
||||
Verify that image is uploaded successfully.
|
||||
|
||||
Arguments:
|
||||
course_id: ID of course
|
||||
edx_video_id: ID of video
|
||||
upload_response: Upload response object
|
||||
|
||||
Returns:
|
||||
uploaded image url
|
||||
"""
|
||||
self.assertEqual(upload_response.status_code, 200)
|
||||
response = json.loads(upload_response.content)
|
||||
val_image_url = get_course_video_image_url(course_id=course_id, edx_video_id=edx_video_id)
|
||||
self.assertEqual(response['image_url'], val_image_url)
|
||||
|
||||
return val_image_url
|
||||
|
||||
def test_video_image(self):
|
||||
"""
|
||||
Test video image is saved.
|
||||
"""
|
||||
edx_video_id = 'test1'
|
||||
video_image_upload_url = self.get_url_for_course_key(self.course.id, {'edx_video_id': edx_video_id})
|
||||
with make_image_file() as image_file:
|
||||
response = self.client.post(video_image_upload_url, {'file': image_file}, format='multipart')
|
||||
image_url1 = self.verify_image_upload_reponse(self.course.id, edx_video_id, response)
|
||||
|
||||
# upload again to verify that new image is uploaded successfully
|
||||
with make_image_file() as image_file:
|
||||
response = self.client.post(video_image_upload_url, {'file': image_file}, format='multipart')
|
||||
image_url2 = self.verify_image_upload_reponse(self.course.id, edx_video_id, response)
|
||||
|
||||
self.assertNotEqual(image_url1, image_url2)
|
||||
|
||||
def test_video_image_no_file(self):
|
||||
"""
|
||||
Test that an error error message is returned if upload request is incorrect.
|
||||
"""
|
||||
video_image_upload_url = self.get_url_for_course_key(self.course.id, {'edx_video_id': 'test1'})
|
||||
response = self.client.post(video_image_upload_url, {})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response = json.loads(response.content)
|
||||
self.assertEqual(response['error'], 'No file provided for video image')
|
||||
|
||||
def test_default_video_image(self):
|
||||
"""
|
||||
Test default video image.
|
||||
"""
|
||||
edx_video_id = 'test1'
|
||||
default_video_image_url = _get_default_video_image_url()
|
||||
get_videos_url = reverse_course_url('videos_handler', self.course.id)
|
||||
video_image_upload_url = self.get_url_for_course_key(self.course.id, {'edx_video_id': edx_video_id})
|
||||
with make_image_file() as image_file:
|
||||
self.client.post(video_image_upload_url, {'file': image_file}, format='multipart')
|
||||
|
||||
val_image_url = get_course_video_image_url(course_id=self.course.id, edx_video_id=edx_video_id)
|
||||
|
||||
response = self.client.get_json(get_videos_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response_videos = json.loads(response.content)["videos"]
|
||||
for response_video in response_videos:
|
||||
if response_video['edx_video_id'] == edx_video_id:
|
||||
self.assertEqual(response_video['course_video_image_url'], val_image_url)
|
||||
else:
|
||||
self.assertEqual(response_video['course_video_image_url'], default_video_image_url)
|
||||
|
||||
|
||||
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_VIDEO_UPLOAD_PIPELINE": True})
|
||||
@override_settings(VIDEO_UPLOAD_PIPELINE={"BUCKET": "test_bucket", "ROOT_PATH": "test_root"})
|
||||
class VideoUrlsCsvTestCase(VideoUploadTestMixin, CourseTestCase):
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
"""
|
||||
Views related to the video upload feature
|
||||
"""
|
||||
from contextlib import closing
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
|
||||
from boto import s3
|
||||
import csv
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
@@ -10,17 +15,19 @@ import rfc6266
|
||||
from boto import s3
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||
from django.http import HttpResponse, HttpResponseNotFound
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_noop
|
||||
from django.views.decorators.http import require_GET, require_http_methods
|
||||
from django.views.decorators.http import require_GET, require_POST, require_http_methods
|
||||
from edxval.api import (
|
||||
SortDirection,
|
||||
VideoSortField,
|
||||
create_video,
|
||||
get_videos_for_course,
|
||||
remove_video_for_course,
|
||||
update_video_status
|
||||
update_video_status,
|
||||
update_video_image
|
||||
)
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
@@ -31,7 +38,8 @@ from util.json_request import JsonResponse, expect_json
|
||||
|
||||
from .course import get_course_and_check_access
|
||||
|
||||
__all__ = ["videos_handler", "video_encodings_download"]
|
||||
|
||||
__all__ = ['videos_handler', 'video_encodings_download', 'video_images_handler']
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -145,6 +153,26 @@ def videos_handler(request, course_key_string, edx_video_id=None):
|
||||
return videos_post(course, request)
|
||||
|
||||
|
||||
@expect_json
|
||||
@login_required
|
||||
@require_POST
|
||||
def video_images_handler(request, course_key_string, edx_video_id=None):
|
||||
if 'file' not in request.FILES:
|
||||
return JsonResponse({"error": _(u'No file provided for video image')}, status=400)
|
||||
|
||||
image_file = request.FILES['file']
|
||||
file_name = request.FILES['file'].name
|
||||
|
||||
# TODO: Image file validation
|
||||
with closing(image_file):
|
||||
image_url = update_video_image(edx_video_id, course_key_string, image_file, file_name)
|
||||
LOGGER.info(
|
||||
'VIDEOS: Video image uploaded for edx_video_id [%s] in course [%s]', edx_video_id, course_key_string
|
||||
)
|
||||
|
||||
return JsonResponse({'image_url': image_url})
|
||||
|
||||
|
||||
@login_required
|
||||
@require_GET
|
||||
def video_encodings_download(request, course_key_string):
|
||||
@@ -296,17 +324,39 @@ def _get_videos(course):
|
||||
return videos
|
||||
|
||||
|
||||
def _get_default_video_image_url():
|
||||
"""
|
||||
Returns default video image url
|
||||
"""
|
||||
return staticfiles_storage.url(settings.VIDEO_IMAGE_DEFAULT_FILENAME)
|
||||
|
||||
|
||||
def _get_index_videos(course):
|
||||
"""
|
||||
Returns the information about each video upload required for the video list
|
||||
"""
|
||||
return list(
|
||||
{
|
||||
attr: video[attr]
|
||||
for attr in ["edx_video_id", "client_video_id", "created", "duration", "status"]
|
||||
}
|
||||
for video in _get_videos(course)
|
||||
)
|
||||
course_id = unicode(course.id)
|
||||
default_video_image_url = _get_default_video_image_url()
|
||||
attrs = ['edx_video_id', 'client_video_id', 'created', 'duration', 'status', 'courses']
|
||||
|
||||
def _get_values(video):
|
||||
"""
|
||||
Get data for predefined video attributes.
|
||||
"""
|
||||
values = {}
|
||||
for attr in attrs:
|
||||
if attr == 'courses':
|
||||
course = filter(lambda c: course_id in c, video['courses'])
|
||||
(__, image_url), = course[0].items()
|
||||
values['course_video_image_url'] = image_url or default_video_image_url
|
||||
else:
|
||||
values[attr] = video[attr]
|
||||
|
||||
return values
|
||||
|
||||
return [
|
||||
_get_values(video) for video in _get_videos(course)
|
||||
]
|
||||
|
||||
|
||||
def videos_index_html(course):
|
||||
@@ -314,15 +364,16 @@ def videos_index_html(course):
|
||||
Returns an HTML page to display previous video uploads and allow new ones
|
||||
"""
|
||||
return render_to_response(
|
||||
"videos_index.html",
|
||||
'videos_index.html',
|
||||
{
|
||||
"context_course": course,
|
||||
"video_handler_url": reverse_course_url("videos_handler", unicode(course.id)),
|
||||
"encodings_download_url": reverse_course_url("video_encodings_download", unicode(course.id)),
|
||||
"previous_uploads": _get_index_videos(course),
|
||||
"concurrent_upload_limit": settings.VIDEO_UPLOAD_PIPELINE.get("CONCURRENT_UPLOAD_LIMIT", 0),
|
||||
"video_supported_file_formats": VIDEO_SUPPORTED_FILE_FORMATS.keys(),
|
||||
"video_upload_max_file_size": VIDEO_UPLOAD_MAX_FILE_SIZE_GB
|
||||
'context_course': course,
|
||||
'image_upload_url': reverse_course_url('video_images_handler', unicode(course.id)),
|
||||
'video_handler_url': reverse_course_url('videos_handler', unicode(course.id)),
|
||||
'encodings_download_url': reverse_course_url('video_encodings_download', unicode(course.id)),
|
||||
'previous_uploads': _get_index_videos(course),
|
||||
'concurrent_upload_limit': settings.VIDEO_UPLOAD_PIPELINE.get('CONCURRENT_UPLOAD_LIMIT', 0),
|
||||
'video_supported_file_formats': VIDEO_SUPPORTED_FILE_FORMATS.keys(),
|
||||
'video_upload_max_file_size': VIDEO_UPLOAD_MAX_FILE_SIZE_GB
|
||||
}
|
||||
)
|
||||
|
||||
@@ -331,12 +382,13 @@ def videos_index_json(course):
|
||||
"""
|
||||
Returns JSON in the following format:
|
||||
{
|
||||
"videos": [{
|
||||
"edx_video_id": "aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa",
|
||||
"client_video_id": "video.mp4",
|
||||
"created": "1970-01-01T00:00:00Z",
|
||||
"duration": 42.5,
|
||||
"status": "upload"
|
||||
'videos': [{
|
||||
'edx_video_id': 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa',
|
||||
'client_video_id': 'video.mp4',
|
||||
'created': '1970-01-01T00:00:00Z',
|
||||
'duration': 42.5,
|
||||
'status': 'upload',
|
||||
'course_video_image_url': 'https://video/images/1234.jpg'
|
||||
}]
|
||||
}
|
||||
"""
|
||||
@@ -364,29 +416,29 @@ def videos_post(course, request):
|
||||
The returned array corresponds exactly to the input array.
|
||||
"""
|
||||
error = None
|
||||
if "files" not in request.json:
|
||||
if 'files' not in request.json:
|
||||
error = "Request object is not JSON or does not contain 'files'"
|
||||
elif any(
|
||||
"file_name" not in file or "content_type" not in file
|
||||
for file in request.json["files"]
|
||||
'file_name' not in file or 'content_type' not in file
|
||||
for file in request.json['files']
|
||||
):
|
||||
error = "Request 'files' entry does not contain 'file_name' and 'content_type'"
|
||||
elif any(
|
||||
file['content_type'] not in VIDEO_SUPPORTED_FILE_FORMATS.values()
|
||||
for file in request.json["files"]
|
||||
for file in request.json['files']
|
||||
):
|
||||
error = "Request 'files' entry contain unsupported content_type"
|
||||
|
||||
if error:
|
||||
return JsonResponse({"error": error}, status=400)
|
||||
return JsonResponse({'error': error}, status=400)
|
||||
|
||||
bucket = storage_service_bucket()
|
||||
course_video_upload_token = course.video_upload_pipeline["course_video_upload_token"]
|
||||
req_files = request.json["files"]
|
||||
course_video_upload_token = course.video_upload_pipeline['course_video_upload_token']
|
||||
req_files = request.json['files']
|
||||
resp_files = []
|
||||
|
||||
for req_file in req_files:
|
||||
file_name = req_file["file_name"]
|
||||
file_name = req_file['file_name']
|
||||
|
||||
try:
|
||||
file_name.encode('ascii')
|
||||
@@ -397,30 +449,30 @@ def videos_post(course, request):
|
||||
edx_video_id = unicode(uuid4())
|
||||
key = storage_service_key(bucket, file_name=edx_video_id)
|
||||
for metadata_name, value in [
|
||||
("course_video_upload_token", course_video_upload_token),
|
||||
("client_video_id", file_name),
|
||||
("course_key", unicode(course.id)),
|
||||
('course_video_upload_token', course_video_upload_token),
|
||||
('client_video_id', file_name),
|
||||
('course_key', unicode(course.id)),
|
||||
]:
|
||||
key.set_metadata(metadata_name, value)
|
||||
upload_url = key.generate_url(
|
||||
KEY_EXPIRATION_IN_SECONDS,
|
||||
"PUT",
|
||||
headers={"Content-Type": req_file["content_type"]}
|
||||
'PUT',
|
||||
headers={'Content-Type': req_file['content_type']}
|
||||
)
|
||||
|
||||
# persist edx_video_id in VAL
|
||||
create_video({
|
||||
"edx_video_id": edx_video_id,
|
||||
"status": "upload",
|
||||
"client_video_id": file_name,
|
||||
"duration": 0,
|
||||
"encoded_videos": [],
|
||||
"courses": [course.id]
|
||||
'edx_video_id': edx_video_id,
|
||||
'status': 'upload',
|
||||
'client_video_id': file_name,
|
||||
'duration': 0,
|
||||
'encoded_videos': [],
|
||||
'courses': [unicode(course.id)]
|
||||
})
|
||||
|
||||
resp_files.append({"file_name": file_name, "upload_url": upload_url, "edx_video_id": edx_video_id})
|
||||
resp_files.append({'file_name': file_name, 'upload_url': upload_url, 'edx_video_id': edx_video_id})
|
||||
|
||||
return JsonResponse({"files": resp_files}, status=200)
|
||||
return JsonResponse({'files': resp_files}, status=200)
|
||||
|
||||
|
||||
def storage_service_bucket():
|
||||
|
||||
@@ -440,6 +440,10 @@ ADVANCED_PROBLEM_TYPES = ENV_TOKENS.get('ADVANCED_PROBLEM_TYPES', ADVANCED_PROBL
|
||||
|
||||
VIDEO_UPLOAD_PIPELINE = ENV_TOKENS.get('VIDEO_UPLOAD_PIPELINE', VIDEO_UPLOAD_PIPELINE)
|
||||
|
||||
################ VIDEO IMAGE STORAGE ###############
|
||||
|
||||
VIDEO_IMAGE_SETTINGS = ENV_TOKENS.get('VIDEO_IMAGE_SETTINGS', VIDEO_IMAGE_SETTINGS)
|
||||
|
||||
################ PUSH NOTIFICATIONS ###############
|
||||
|
||||
PARSE_KEYS = AUTH_TOKENS.get("PARSE_KEYS", {})
|
||||
|
||||
@@ -103,6 +103,8 @@ from lms.envs.common import (
|
||||
CONTACT_EMAIL,
|
||||
|
||||
DISABLE_ACCOUNT_ACTIVATION_REQUIREMENT_SWITCH,
|
||||
# Video Image settings
|
||||
VIDEO_IMAGE_SETTINGS,
|
||||
)
|
||||
from path import Path as path
|
||||
from warnings import simplefilter
|
||||
@@ -1344,3 +1346,7 @@ PROFILE_IMAGE_SIZES_MAP = {
|
||||
'medium': 50,
|
||||
'small': 30
|
||||
}
|
||||
|
||||
###################### VIDEO IMAGE STORAGE ######################
|
||||
|
||||
VIDEO_IMAGE_DEFAULT_FILENAME = 'default_video_image.png'
|
||||
|
||||
@@ -335,3 +335,13 @@ FEATURES['CUSTOM_COURSES_EDX'] = True
|
||||
|
||||
# API access management -- needed for simple-history to run.
|
||||
INSTALLED_APPS += ('openedx.core.djangoapps.api_admin',)
|
||||
|
||||
########################## VIDEO IMAGE STORAGE ############################
|
||||
VIDEO_IMAGE_SETTINGS = dict(
|
||||
STORAGE_KWARGS=dict(
|
||||
location=MEDIA_ROOT,
|
||||
base_url=MEDIA_URL,
|
||||
),
|
||||
DIRECTORY_PREFIX='videoimage/',
|
||||
)
|
||||
VIDEO_IMAGE_DEFAULT_FILENAME = 'default_video_image.png'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.conf import settings
|
||||
from django.conf.urls import include, patterns, url
|
||||
from django.conf.urls.static import static
|
||||
# There is a course creators admin table.
|
||||
from ratelimitbackend import admin
|
||||
|
||||
@@ -112,6 +113,7 @@ urlpatterns += patterns(
|
||||
url(r'^textbooks/{}$'.format(settings.COURSE_KEY_PATTERN), 'textbooks_list_handler'),
|
||||
url(r'^textbooks/{}/(?P<textbook_id>\d[^/]*)$'.format(settings.COURSE_KEY_PATTERN), 'textbooks_detail_handler'),
|
||||
url(r'^videos/{}(?:/(?P<edx_video_id>[-\w]+))?$'.format(settings.COURSE_KEY_PATTERN), 'videos_handler'),
|
||||
url(r'^video_images/{}(?:/(?P<edx_video_id>[-\w]+))?$'.format(settings.COURSE_KEY_PATTERN), 'video_images_handler'),
|
||||
url(r'^video_encodings_download/{}$'.format(settings.COURSE_KEY_PATTERN), 'video_encodings_download'),
|
||||
url(r'^group_configurations/{}$'.format(settings.COURSE_KEY_PATTERN), 'group_configurations_list_handler'),
|
||||
url(r'^group_configurations/{}/(?P<group_configuration_id>\d+)(/)?(?P<group_id>\d+)?$'.format(
|
||||
@@ -189,6 +191,11 @@ if settings.DEBUG:
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
urlpatterns += static(
|
||||
settings.VIDEO_IMAGE_SETTINGS['STORAGE_KWARGS']['base_url'],
|
||||
document_root=settings.VIDEO_IMAGE_SETTINGS['STORAGE_KWARGS']['location']
|
||||
)
|
||||
|
||||
if 'debug_toolbar' in settings.INSTALLED_APPS:
|
||||
import debug_toolbar
|
||||
urlpatterns += (
|
||||
|
||||
@@ -18,7 +18,7 @@ import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from lxml import etree
|
||||
from mock import ANY, Mock, patch
|
||||
from mock import ANY, Mock, patch, MagicMock
|
||||
import ddt
|
||||
|
||||
from django.conf import settings
|
||||
@@ -673,7 +673,7 @@ class VideoExportTestCase(VideoDescriptorTestBase):
|
||||
"""
|
||||
Test that we write the correct XML on export.
|
||||
"""
|
||||
def mock_val_export(edx_video_id):
|
||||
def mock_val_export(edx_video_id, course_id):
|
||||
"""Mock edxval.api.export_to_xml"""
|
||||
return etree.Element(
|
||||
'video_asset',
|
||||
@@ -695,6 +695,7 @@ class VideoExportTestCase(VideoDescriptorTestBase):
|
||||
self.descriptor.download_video = True
|
||||
self.descriptor.transcripts = {'ua': 'ukrainian_translation.srt', 'ge': 'german_translation.srt'}
|
||||
self.descriptor.edx_video_id = 'test_edx_video_id'
|
||||
self.descriptor.runtime.course_id = MagicMock()
|
||||
|
||||
xml = self.descriptor.definition_to_xml(None) # We don't use the `resource_fs` parameter
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
@@ -718,6 +719,7 @@ class VideoExportTestCase(VideoDescriptorTestBase):
|
||||
mock_val_api.ValVideoNotFoundError = _MockValVideoNotFoundError
|
||||
mock_val_api.export_to_xml = Mock(side_effect=mock_val_api.ValVideoNotFoundError)
|
||||
self.descriptor.edx_video_id = 'test_edx_video_id'
|
||||
self.descriptor.runtime.course_id = MagicMock()
|
||||
|
||||
xml = self.descriptor.definition_to_xml(None)
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
|
||||
@@ -653,7 +653,10 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
|
||||
|
||||
if self.edx_video_id and edxval_api:
|
||||
try:
|
||||
xml.append(edxval_api.export_to_xml(self.edx_video_id))
|
||||
xml.append(edxval_api.export_to_xml(
|
||||
self.edx_video_id,
|
||||
unicode(self.runtime.course_id.for_branch(None)))
|
||||
)
|
||||
except edxval_api.ValVideoNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
@@ -1261,7 +1261,7 @@ class TestVideoDescriptorStudentViewJson(TestCase):
|
||||
'duration': self.TEST_DURATION,
|
||||
'status': 'dummy',
|
||||
'encoded_videos': [self.TEST_ENCODED_VIDEO],
|
||||
'courses': [self.video.location.course_key] if associate_course_in_val else [],
|
||||
'courses': [unicode(self.video.location.course_key)] if associate_course_in_val else [],
|
||||
})
|
||||
self.val_video = get_video_info(self.TEST_EDX_VIDEO_ID) # pylint: disable=attribute-defined-outside-init
|
||||
|
||||
@@ -1391,6 +1391,7 @@ class VideoDescriptorTest(TestCase, VideoDescriptorTestBase):
|
||||
def setUp(self):
|
||||
super(VideoDescriptorTest, self).setUp()
|
||||
self.descriptor.runtime.handler_url = MagicMock()
|
||||
self.descriptor.runtime.course_id = MagicMock()
|
||||
|
||||
def test_get_context(self):
|
||||
""""
|
||||
@@ -1438,7 +1439,7 @@ class VideoDescriptorTest(TestCase, VideoDescriptorTestBase):
|
||||
actual = self.descriptor.definition_to_xml(resource_fs=None)
|
||||
expected_str = """
|
||||
<video download_video="false" url_name="SampleProblem">
|
||||
<video_asset client_video_id="test_client_video_id" duration="111.0">
|
||||
<video_asset client_video_id="test_client_video_id" duration="111.0" image="">
|
||||
<encoded_video profile="mobile" url="http://example.com/video" file_size="222" bitrate="333"/>
|
||||
</video_asset>
|
||||
</video>
|
||||
@@ -1474,7 +1475,7 @@ class VideoDescriptorTest(TestCase, VideoDescriptorTestBase):
|
||||
self.assertEqual(video_data['client_video_id'], 'test_client_video_id')
|
||||
self.assertEqual(video_data['duration'], 111)
|
||||
self.assertEqual(video_data['status'], 'imported')
|
||||
self.assertEqual(video_data['courses'], [id_generator.target_course_id])
|
||||
self.assertEqual(video_data['courses'], [{id_generator.target_course_id: None}])
|
||||
self.assertEqual(video_data['encoded_videos'][0]['profile'], 'mobile')
|
||||
self.assertEqual(video_data['encoded_videos'][0]['url'], 'http://example.com/video')
|
||||
self.assertEqual(video_data['encoded_videos'][0]['file_size'], 222)
|
||||
|
||||
@@ -2564,6 +2564,20 @@ MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS = 15 * 60
|
||||
TIME_ZONE_DISPLAYED_FOR_DEADLINES = 'UTC'
|
||||
|
||||
|
||||
########################## VIDEO IMAGE STORAGE ############################
|
||||
|
||||
VIDEO_IMAGE_SETTINGS = dict(
|
||||
# Backend storage
|
||||
# STORAGE_CLASS='storages.backends.s3boto.S3BotoStorage',
|
||||
# STORAGE_KWARGS=dict(bucket='video-image-bucket'),
|
||||
STORAGE_KWARGS=dict(
|
||||
location=MEDIA_ROOT,
|
||||
base_url=MEDIA_URL,
|
||||
),
|
||||
DIRECTORY_PREFIX='videoimage/',
|
||||
)
|
||||
|
||||
|
||||
# Source:
|
||||
# http://loc.gov/standards/iso639-2/ISO-639-2_utf-8.txt according to http://en.wikipedia.org/wiki/ISO_639-1
|
||||
# Note that this is used as the set of choices to the `code` field of the
|
||||
|
||||
@@ -77,7 +77,7 @@ git+https://github.com/edx/lettuce.git@0.2.20.002#egg=lettuce==0.2.20.002
|
||||
git+https://github.com/edx/edx-ora2.git@1.4.3#egg=ora2==1.4.3
|
||||
-e git+https://github.com/edx/edx-submissions.git@2.0.0#egg=edx-submissions==2.0.0
|
||||
git+https://github.com/edx/ease.git@release-2015-07-14#egg=ease==0.1.3
|
||||
git+https://github.com/edx/edx-val.git@0.0.13#egg=edxval==0.0.13
|
||||
git+https://github.com/edx/edx-val.git@0.0.14#egg=edxval==0.0.14
|
||||
git+https://github.com/pmitros/RecommenderXBlock.git@v1.2#egg=recommender-xblock==1.2
|
||||
git+https://github.com/solashirai/crowdsourcehinter.git@518605f0a95190949fe77bd39158450639e2e1dc#egg=crowdsourcehinter-xblock==0.1
|
||||
-e git+https://github.com/pmitros/RateXBlock.git@367e19c0f6eac8a5f002fd0f1559555f8e74bfff#egg=rate-xblock
|
||||
|
||||
Reference in New Issue
Block a user