Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
dca12d65
Commit
dca12d65
authored
Apr 11, 2017
by
Muhammad Ammar
Committed by
Mushtaq Ali
Jul 06, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Save or update course video image - TNL-6762
parent
7b910953
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
282 additions
and
92 deletions
+282
-92
cms/djangoapps/contentstore/tests/test_contentstore.py
+1
-1
cms/djangoapps/contentstore/views/tests/test_videos.py
+130
-39
cms/djangoapps/contentstore/views/videos.py
+97
-45
cms/envs/aws.py
+4
-0
cms/envs/common.py
+6
-0
cms/envs/test.py
+10
-0
cms/urls.py
+7
-0
common/lib/xmodule/xmodule/tests/test_video.py
+4
-2
common/lib/xmodule/xmodule/video_module/video_module.py
+4
-1
lms/djangoapps/courseware/tests/test_video_mongo.py
+4
-3
lms/envs/common.py
+14
-0
requirements/edx/github.txt
+1
-1
No files found.
cms/djangoapps/contentstore/tests/test_contentstore.py
View file @
dca12d65
...
...
@@ -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
=
[]
...
...
cms/djangoapps/contentstore/views/tests/test_videos.py
View file @
dca12d65
...
...
@@ -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
(
VideoUploadTest
Mixin
,
self
)
.
setUp
()
super
(
VideoUploadTest
Base
,
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
):
...
...
cms/djangoapps/contentstore/views/videos.py
View file @
dca12d65
"""
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
():
...
...
cms/envs/aws.py
View file @
dca12d65
...
...
@@ -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"
,
{})
...
...
cms/envs/common.py
View file @
dca12d65
...
...
@@ -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'
cms/envs/test.py
View file @
dca12d65
...
...
@@ -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'
cms/urls.py
View file @
dca12d65
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
+=
(
...
...
common/lib/xmodule/xmodule/tests/test_video.py
View file @
dca12d65
...
...
@@ -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
)
...
...
common/lib/xmodule/xmodule/video_module/video_module.py
View file @
dca12d65
...
...
@@ -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
...
...
lms/djangoapps/courseware/tests/test_video_mongo.py
View file @
dca12d65
...
...
@@ -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
)
...
...
lms/envs/common.py
View file @
dca12d65
...
...
@@ -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
...
...
requirements/edx/github.txt
View file @
dca12d65
...
...
@@ -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.1
3#egg=edxval==0.0.13
git+https://github.com/edx/edx-val.git@0.0.1
4#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
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment