feat: add drf for studio video page (#33528)

This commit is contained in:
Kristin Aoki
2023-10-27 09:04:37 -04:00
committed by GitHub
parent 3c50f3e029
commit 8cfd04ff68
9 changed files with 543 additions and 64 deletions

View File

@@ -14,6 +14,11 @@ from .proctoring import (
)
from .settings import CourseSettingsSerializer
from .xblock import XblockSerializer
from .videos import VideoUploadSerializer, VideoImageSerializer
from .videos import (
CourseVideosSerializer,
VideoUploadSerializer,
VideoImageSerializer,
VideoUsageSerializer
)
from .transcripts import TranscriptSerializer
from .assets import AssetSerializer

View File

@@ -11,6 +11,91 @@ class FileSpecSerializer(StrictSerializer):
content_type = serializers.ChoiceField(choices=['video/mp4', 'video/webm', 'video/ogg'])
class VideoImageSettingsSerializer(serializers.Serializer):
"""Serializer for image settings"""
video_image_upload_enabled = serializers.BooleanField()
max_size = serializers.IntegerField()
min_size = serializers.IntegerField()
max_width = serializers.IntegerField()
max_height = serializers.IntegerField()
supported_file_formats = serializers.DictField(
child=serializers.CharField()
)
class VideoTranscriptSettingsSerializer(serializers.Serializer):
"""Serializer for transcript settings"""
transcript_download_handler_url = serializers.CharField()
transcript_upload_handler_url = serializers.CharField()
transcript_delete_handler_url = serializers.CharField()
trancript_download_file_format = serializers.CharField()
transcript_preferences_handler_url = serializers.CharField(required=False, allow_null=True)
transcript_credentials_handler_url = serializers.CharField(required=False, allow_null=True)
transcription_plans = serializers.DictField(
child=serializers.DictField(),
required=False,
allow_null=True,
)
class VideoModelSerializer(serializers.Serializer):
"""Serializer for a video"""
client_video_id = serializers.CharField()
course_video_image_url = serializers.CharField()
created = serializers.CharField()
duration = serializers.FloatField()
edx_video_id = serializers.CharField()
error_description = serializers.CharField()
status = serializers.CharField()
file_size = serializers.IntegerField()
download_link = serializers.CharField()
transcript_urls = serializers.DictField(
child=serializers.CharField()
)
transcription_status = serializers.CharField()
transcripts = serializers.ListField(
child=serializers.CharField()
)
class CourseVideosSerializer(serializers.Serializer):
"""Serializer for course videos"""
image_upload_url = serializers.CharField()
video_handler_url = serializers.CharField()
encodings_download_url = serializers.CharField()
default_video_image_url = serializers.CharField()
previous_uploads = VideoModelSerializer(many=True, required=False)
concurrent_upload_limit = serializers.IntegerField()
video_supported_file_formats = serializers.ListField(
child=serializers.CharField()
)
video_upload_max_file_size = serializers.CharField()
video_image_settings = VideoImageSettingsSerializer(required=True, allow_null=False)
is_video_transcript_enabled = serializers.BooleanField()
active_transcript_preferences = serializers.BooleanField(required=False, allow_null=True)
transcript_credentials = serializers.DictField(
child=serializers.CharField()
)
transcript_available_languages = serializers.ListField(
child=serializers.DictField(
child=serializers.CharField()
)
)
video_transcript_settings = VideoTranscriptSettingsSerializer()
pagination_context = serializers.DictField(
child=serializers.CharField(),
required=False,
allow_null=True,
)
class VideoUsageSerializer(serializers.Serializer):
"""Serializer for video usage"""
usage_locations = serializers.ListField(
child=serializers.CharField()
)
class VideoUploadSerializer(StrictSerializer):
"""
Strict Serializer for video upload urls.

View File

@@ -11,6 +11,7 @@ from .views import (
CourseGradingView,
CourseRerunView,
CourseSettingsView,
CourseVideosView,
HomePageView,
ProctoredExamSettingsView,
ProctoringErrorsView,
@@ -19,6 +20,7 @@ from .views import (
videos,
transcripts,
HelpUrlsView,
VideoUsageView
)
app_name = 'v1'
@@ -31,6 +33,16 @@ urlpatterns = [
HomePageView.as_view(),
name="home"
),
re_path(
fr'^videos/{COURSE_ID_PATTERN}$',
CourseVideosView.as_view(),
name="course_videos"
),
re_path(
fr'^videos/{COURSE_ID_PATTERN}/{VIDEO_ID_PATTERN}/usage$',
VideoUsageView.as_view(),
name="video_usage"
),
re_path(
fr'^proctored_exam_settings/{COURSE_ID_PATTERN}$',
ProctoredExamSettingsView.as_view(),

View File

@@ -11,10 +11,12 @@ from .settings import CourseSettingsView
from .xblock import XblockView, XblockCreateView
from .assets import AssetsCreateRetrieveView, AssetsUpdateDestroyView
from .videos import (
CourseVideosView,
VideosUploadsView,
VideosCreateUploadView,
VideoImagesView,
VideoEncodingsDownloadView,
VideoFeaturesView
VideoFeaturesView,
VideoUsageView,
)
from .help_urls import HelpUrlsView

View File

@@ -0,0 +1,128 @@
"""
Unit tests for course settings views.
"""
import ddt
from django.conf import settings
from django.contrib.staticfiles.storage import staticfiles_storage
from django.urls import reverse
from edx_toggles.toggles import WaffleSwitch
from edx_toggles.toggles.testutils import override_waffle_switch
from edxval.api import (
get_3rd_party_transcription_plans,
get_transcript_credentials_state_for_org,
get_transcript_preferences,
)
from mock import patch
from rest_framework import status
from cms.djangoapps.contentstore.video_storage_handlers import get_all_transcript_languages
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
from cms.djangoapps.contentstore.utils import reverse_course_url
from ...mixins import PermissionAccessMixin
@ddt.ddt
class CourseVideosViewTest(CourseTestCase, PermissionAccessMixin):
"""
Tests for CourseVideosView.
"""
def setUp(self):
super().setUp()
self.url = reverse(
"cms.djangoapps.contentstore:v1:course_videos",
kwargs={"course_id": self.course.id},
)
def test_course_videos_response(self):
"""Check successful response content"""
response = self.client.get(self.url)
expected_response = {
"image_upload_url": reverse_course_url("video_images_handler", str(self.course.id)),
"video_handler_url": reverse_course_url("videos_handler", str(self.course.id)),
"encodings_download_url": reverse_course_url("video_encodings_download", str(self.course.id)),
"default_video_image_url": staticfiles_storage.url(settings.VIDEO_IMAGE_DEFAULT_FILENAME),
"previous_uploads": [],
"concurrent_upload_limit": settings.VIDEO_UPLOAD_PIPELINE.get("CONCURRENT_UPLOAD_LIMIT", 0),
"video_supported_file_formats": [".mp4", ".mov"],
"video_upload_max_file_size": "5",
"video_image_settings": {
"video_image_upload_enabled": False,
"max_size": settings.VIDEO_IMAGE_SETTINGS["VIDEO_IMAGE_MAX_BYTES"],
"min_size": settings.VIDEO_IMAGE_SETTINGS["VIDEO_IMAGE_MIN_BYTES"],
"max_width": settings.VIDEO_IMAGE_MAX_WIDTH,
"max_height": settings.VIDEO_IMAGE_MAX_HEIGHT,
"supported_file_formats": settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS
},
"is_video_transcript_enabled": False,
"active_transcript_preferences": None,
"transcript_credentials": None,
"transcript_available_languages": get_all_transcript_languages(),
"video_transcript_settings": {
"transcript_download_handler_url": reverse('transcript_download_handler'),
"transcript_upload_handler_url": reverse('transcript_upload_handler'),
"transcript_delete_handler_url": reverse_course_url("transcript_delete_handler", str(self.course.id)),
"trancript_download_file_format": "srt",
"transcript_preferences_handler_url": None,
"transcript_credentials_handler_url": None,
"transcription_plans": None
},
"pagination_context": {}
}
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertDictEqual(expected_response, response.data)
@override_waffle_switch(WaffleSwitch( # lint-amnesty, pylint: disable=toggle-missing-annotation
'videos.video_image_upload_enabled', __name__
), True)
def test_video_image_upload_enabled(self):
"""
Make sure if the feature flag is enabled we have updated the dict keys in response.
"""
response = self.client.get(self.url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn("video_image_settings", response.data)
imageSettings = response.data["video_image_settings"]
self.assertIn("video_image_upload_enabled", imageSettings)
self.assertTrue(imageSettings["video_image_upload_enabled"])
def test_VideoTranscriptEnabledFlag_enabled(self):
"""
Make sure if the feature flags are enabled we have updated the dict keys in response.
"""
with patch('openedx.core.djangoapps.video_config.models.VideoTranscriptEnabledFlag.feature_enabled') as feature:
feature.return_value = True
response = self.client.get(self.url)
self.assertIn("is_video_transcript_enabled", response.data)
self.assertTrue(response.data["is_video_transcript_enabled"])
expect_active_preferences = get_transcript_preferences(str(self.course.id))
self.assertIn("active_transcript_preferences", response.data)
self.assertEqual(expect_active_preferences, response.data["active_transcript_preferences"])
expected_credentials = get_transcript_credentials_state_for_org(self.course.id.org)
self.assertIn("transcript_credentials", response.data)
self.assertDictEqual(expected_credentials, response.data["transcript_credentials"])
transcript_settings = response.data["video_transcript_settings"]
expected_plans = get_3rd_party_transcription_plans()
self.assertIn("transcription_plans", transcript_settings)
self.assertDictEqual(expected_plans, transcript_settings["transcription_plans"])
expected_preference_handler = reverse_course_url(
'transcript_preferences_handler',
str(self.course.id)
)
self.assertIn("transcript_preferences_handler_url", transcript_settings)
self.assertEqual(expected_preference_handler, transcript_settings["transcript_preferences_handler_url"])
expected_credentials_handler = reverse_course_url(
'transcript_credentials_handler',
str(self.course.id)
)
self.assertIn("transcript_credentials_handler_url", transcript_settings)
self.assertEqual(expected_credentials_handler, transcript_settings["transcript_credentials_handler_url"])

View File

@@ -1,29 +1,42 @@
"""
Public rest API endpoints for the CMS API video assets.
"""
import edx_api_doc_tools as apidocs
import logging
from opaque_keys.edx.keys import CourseKey
from rest_framework.generics import (
CreateAPIView,
RetrieveAPIView,
DestroyAPIView
)
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.parsers import (MultiPartParser, FormParser)
from django.views.decorators.csrf import csrf_exempt
from django.http import Http404
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes, verify_course_exists
from openedx.core.lib.api.parsers import TypedFileUploadParser
from common.djangoapps.student.auth import has_studio_read_access
from common.djangoapps.util.json_request import expect_json_in_class_view
from ....api import course_author_access_required
from ....utils import get_course_videos_context
from cms.djangoapps.contentstore.video_storage_handlers import (
handle_videos,
get_video_encodings_download,
handle_video_images,
enabled_video_features
enabled_video_features,
get_video_usage_path
)
from cms.djangoapps.contentstore.rest_api.v1.serializers import (
CourseVideosSerializer,
VideoUploadSerializer,
VideoImageSerializer,
VideoUsageSerializer,
)
from cms.djangoapps.contentstore.rest_api.v1.serializers import VideoUploadSerializer, VideoImageSerializer
import cms.djangoapps.contentstore.toggles as contentstore_toggles
from .utils import validate_request_with_serializer
@@ -32,6 +45,161 @@ log = logging.getLogger(__name__)
toggles = contentstore_toggles
@view_auth_classes(is_authenticated=True)
class CourseVideosView(DeveloperErrorViewMixin, APIView):
"""
View for course videos.
"""
@apidocs.schema(
parameters=[
apidocs.string_parameter("course_id", apidocs.ParameterLocation.PATH, description="Course ID"),
],
responses={
200: CourseVideosSerializer,
401: "The requester is not authenticated",
403: "The requester cannot access the specified course",
404: "The requested course does not exist",
},
)
@verify_course_exists()
def get(self, request: Request, course_id: str):
"""
Get an object containing course videos.
**Example Request**
GET /api/contentstore/v1/videos/{course_id}/{edx_video_id}
**Response Values**
If the request is successful, an HTTP 200 "OK" response is returned.
The HTTP 200 response contains a single dict that contains keys that
are the course's videos.
**Example Response**
```json
{
image_upload_url: '/video_images/course_id',
video_handler_url: '/videos/course_id',
encodings_download_url: '/video_encodings_download/course_id',
default_video_image_url: '/static/studio/images/video-images/default_video_image.png',
previous_uploads: [
{
edx_video_id: 'mOckID1',
clientVideoId: 'mOckID1.mp4',
created: '',
courseVideoImageUrl: '/video',
transcripts: [],
status: 'Imported',
file_size: 123,
download_link: 'http:/download_video.com'
},
{
edx_video_id: 'mOckID5',
clientVideoId: 'mOckID5.mp4',
created: '',
courseVideoImageUrl: 'http:/video',
transcripts: ['en'],
status: 'Failed',
file_size: 0,
download_link: ''
},
{
edx_video_id: 'mOckID3',
clientVideoId: 'mOckID3.mp4',
created: '',
courseVideoImageUrl: null,
transcripts: ['en'],
status: 'Ready',
file_size: 123,
download_link: 'http:/download_video.com'
},
],
concurrent_upload_limit: 4,
video_supported_file_formats: ['.mp4', '.mov'],
video_upload_max_file_size: '5',
video_image_settings: {
video_image_upload_enabled: false,
max_size: 2097152,
min_size: 2048,
max_width: 1280,
max_height: 720,
supported_file_formats: {
'.bmp': 'image/bmp',
'.bmp2': 'image/x-ms-bmp',
'.gif': 'image/gif',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.png': 'image/png',
},
},
is_video_transcript_enabled: false,
active_transcript_preferences: null,
transcript_credentials: {},
transcript_available_languages: [{ language_code: 'ab', language_text: 'Abkhazian' }],
video_transcript_settings: {
transcript_download_handler_url: '/transcript_download/',
transcript_upload_handler_url: '/transcript_upload/',
transcript_delete_handler_url: '/transcript_delete/course_id',
trancript_download_file_format: 'srt',
},
pagination_context: {},
}
```
"""
course_key = CourseKey.from_string(course_id)
if not has_studio_read_access(request.user, course_key):
self.permission_denied(request)
course_videos_context = get_course_videos_context(
None,
None,
course_key,
)
serializer = CourseVideosSerializer(course_videos_context)
return Response(serializer.data)
@view_auth_classes(is_authenticated=True)
class VideoUsageView(DeveloperErrorViewMixin, APIView):
"""
View for course video usage locations.
"""
@apidocs.schema(
parameters=[
apidocs.string_parameter("course_id", apidocs.ParameterLocation.PATH, description="Course ID"),
apidocs.string_parameter("edx_video_id", apidocs.ParameterLocation.PATH, description="edX Video ID"),
],
responses={
200: VideoUsageSerializer,
401: "The requester is not authenticated",
403: "The requester cannot access the specified course",
404: "The requested course does not exist",
},
)
@verify_course_exists()
def get(self, request: Request, course_id: str, edx_video_id: str):
"""
Get an object containing course videos.
**Example Request**
GET /api/contentstore/v1/videos/{course_id}/{edx_video_id}
**Response Values**
If the request is successful, an HTTP 200 "OK" response is returned.
The HTTP 200 response contains a single dict that contains keys that
are the course's videos.
**Example Response**
```json
{
"usage_locations": ["subsection - unit/xblock"],
}
```
"""
course_key = CourseKey.from_string(course_id)
if not has_studio_read_access(request.user, course_key):
self.permission_denied(request)
usage_locations = get_video_usage_path(request, course_key, edx_video_id)
serializer = VideoUsageSerializer(usage_locations)
return Response(serializer.data)
@view_auth_classes()
class VideosUploadsView(DeveloperErrorViewMixin, RetrieveAPIView, DestroyAPIView):
"""

View File

@@ -1568,6 +1568,92 @@ def get_course_rerun_context(course_key, course_block, user):
return course_rerun_context
def get_course_videos_context(course_block, pagination_conf, course_key=None):
"""
Utils is used to get contest of course videos.
It is used for both DRF and django views.
"""
from edx_toggles.toggles import WaffleSwitch
from edxval.api import (
get_3rd_party_transcription_plans,
get_transcript_credentials_state_for_org,
get_transcript_preferences,
)
from openedx.core.djangoapps.video_config.models import VideoTranscriptEnabledFlag
from xmodule.video_block.transcripts_utils import Transcript # lint-amnesty, pylint: disable=wrong-import-order
from .video_storage_handlers import (
get_all_transcript_languages,
_get_index_videos,
_get_default_video_image_url
)
VIDEO_SUPPORTED_FILE_FORMATS = {
'.mp4': 'video/mp4',
'.mov': 'video/quicktime',
}
VIDEO_UPLOAD_MAX_FILE_SIZE_GB = 5
# Waffle switch for enabling/disabling video image upload feature
VIDEO_IMAGE_UPLOAD_ENABLED = WaffleSwitch( # lint-amnesty, pylint: disable=toggle-missing-annotation
'videos.video_image_upload_enabled', __name__
)
course = course_block
if not course:
with modulestore().bulk_operations(course_key):
course = modulestore().get_course(course_key)
is_video_transcript_enabled = VideoTranscriptEnabledFlag.feature_enabled(course.id)
previous_uploads, pagination_context = _get_index_videos(course, pagination_conf)
course_video_context = {
'context_course': course,
'image_upload_url': reverse_course_url('video_images_handler', str(course.id)),
'video_handler_url': reverse_course_url('videos_handler', str(course.id)),
'encodings_download_url': reverse_course_url('video_encodings_download', str(course.id)),
'default_video_image_url': _get_default_video_image_url(),
'previous_uploads': previous_uploads,
'concurrent_upload_limit': settings.VIDEO_UPLOAD_PIPELINE.get('CONCURRENT_UPLOAD_LIMIT', 0),
'video_supported_file_formats': list(VIDEO_SUPPORTED_FILE_FORMATS.keys()),
'video_upload_max_file_size': VIDEO_UPLOAD_MAX_FILE_SIZE_GB,
'video_image_settings': {
'video_image_upload_enabled': VIDEO_IMAGE_UPLOAD_ENABLED.is_enabled(),
'max_size': settings.VIDEO_IMAGE_SETTINGS['VIDEO_IMAGE_MAX_BYTES'],
'min_size': settings.VIDEO_IMAGE_SETTINGS['VIDEO_IMAGE_MIN_BYTES'],
'max_width': settings.VIDEO_IMAGE_MAX_WIDTH,
'max_height': settings.VIDEO_IMAGE_MAX_HEIGHT,
'supported_file_formats': settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS
},
'is_video_transcript_enabled': is_video_transcript_enabled,
'active_transcript_preferences': None,
'transcript_credentials': None,
'transcript_available_languages': get_all_transcript_languages(),
'video_transcript_settings': {
'transcript_download_handler_url': reverse('transcript_download_handler'),
'transcript_upload_handler_url': reverse('transcript_upload_handler'),
'transcript_delete_handler_url': reverse_course_url('transcript_delete_handler', str(course.id)),
'trancript_download_file_format': Transcript.SRT
},
'pagination_context': pagination_context
}
if is_video_transcript_enabled:
course_video_context['video_transcript_settings'].update({
'transcript_preferences_handler_url': reverse_course_url(
'transcript_preferences_handler',
str(course.id)
),
'transcript_credentials_handler_url': reverse_course_url(
'transcript_credentials_handler',
str(course.id)
),
'transcription_plans': get_3rd_party_transcription_plans(),
})
course_video_context['active_transcript_preferences'] = get_transcript_preferences(str(course.id))
# Cached state for transcript providers' credentials (org-specific)
course_video_context['transcript_credentials'] = get_transcript_credentials_state_for_org(course.id.org)
return course_video_context
class StudioPermissionsService:
"""
Service that can provide information about a user's permissions.

View File

@@ -15,9 +15,9 @@ from boto.s3.connection import S3Connection
from boto import s3
from django.conf import settings
from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.exceptions import PermissionDenied
from django.http import FileResponse, HttpResponseNotFound
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.translation import gettext as _
from django.utils.translation import gettext_noop
from edx_toggles.toggles import WaffleSwitch
@@ -29,7 +29,6 @@ from edxval.api import (
get_3rd_party_transcription_plans,
get_available_transcript_languages,
get_video_transcript_url,
get_transcript_credentials_state_for_org,
get_transcript_preferences,
get_videos_for_course,
remove_transcript_preferences,
@@ -43,6 +42,7 @@ from rest_framework import status as rest_status
from rest_framework.response import Response
from common.djangoapps.edxmako.shortcuts import render_to_response
from common.djangoapps.student.auth import has_course_author_access
from common.djangoapps.util.json_request import JsonResponse
from openedx.core.djangoapps.video_config.models import VideoTranscriptEnabledFlag
from openedx.core.djangoapps.video_config.toggles import PUBLIC_VIDEO_SHARE
@@ -51,11 +51,11 @@ from openedx.core.djangoapps.video_pipeline.config.waffle import (
ENABLE_DEVSTACK_VIDEO_UPLOADS,
)
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag
from xmodule.video_block.transcripts_utils import Transcript # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
from .models import VideoUploadConfig
from .toggles import use_new_video_uploads_page, use_mock_video_uploads
from .utils import reverse_course_url, get_video_uploads_url
from .utils import get_video_uploads_url, get_course_videos_context
from .video_utils import validate_video_image
from .views.course import get_course_and_check_access
@@ -223,6 +223,33 @@ def handle_videos(request, course_key_string, edx_video_id=None):
return JsonResponse(data, status=status)
def get_video_usage_path(request, course_key, edx_video_id):
"""
API for fetching the locations a specific video is used in a course.
Returns a list of paths to a video.
"""
if not has_course_author_access(request.user, course_key):
raise PermissionDenied()
store = modulestore()
usage_locations = []
videos = store.get_items(
course_key,
qualifiers={
'category': 'video'
},
)
for video in videos:
video_id = getattr(video, 'edx_video_id', '')
if video_id == edx_video_id:
unit = video.get_parent()
subsection = unit.get_parent()
subsection_display_name = getattr(subsection, 'display_name', '')
unit_display_name = getattr(unit, 'display_name', '')
xblock_display_name = getattr(video, 'display_name', '')
usage_locations.append(f'{subsection_display_name} - {unit_display_name} / {xblock_display_name}')
return {'usage_locations': usage_locations}
def handle_generate_video_upload_link(request, course_key_string):
"""
API for creating a video upload. Returns an edx_video_id and a presigned URL that can be used
@@ -585,7 +612,7 @@ def _get_index_videos(course, pagination_conf=None):
course_id = str(course.id)
attrs = [
'edx_video_id', 'client_video_id', 'created', 'duration',
'status', 'courses', 'transcripts', 'transcription_status',
'status', 'courses', 'encoded_videos', 'transcripts', 'transcription_status',
'transcript_urls', 'error_description'
]
@@ -598,9 +625,15 @@ def _get_index_videos(course, pagination_conf=None):
if attr == 'courses':
course = [c for c in video['courses'] if course_id in c]
(__, values['course_video_image_url']), = list(course[0].items())
elif attr == 'encoded_videos':
values['download_link'] = ''
values['file_size'] = 0
for encoding in video['encoded_videos']:
if encoding['profile'] == 'desktop_mp4':
values['download_link'] = encoding['url']
values['file_size'] = encoding['file_size']
else:
values[attr] = video[attr]
return values
videos, pagination_context = _get_videos(course, pagination_conf)
@@ -636,54 +669,10 @@ def videos_index_html(course, pagination_conf=None):
"""
Returns an HTML page to display previous video uploads and allow new ones
"""
is_video_transcript_enabled = VideoTranscriptEnabledFlag.feature_enabled(course.id)
previous_uploads, pagination_context = _get_index_videos(course, pagination_conf)
context = {
'context_course': course,
'image_upload_url': reverse_course_url('video_images_handler', str(course.id)),
'video_handler_url': reverse_course_url('videos_handler', str(course.id)),
'encodings_download_url': reverse_course_url('video_encodings_download', str(course.id)),
'default_video_image_url': _get_default_video_image_url(),
'previous_uploads': previous_uploads,
'concurrent_upload_limit': settings.VIDEO_UPLOAD_PIPELINE.get('CONCURRENT_UPLOAD_LIMIT', 0),
'video_supported_file_formats': list(VIDEO_SUPPORTED_FILE_FORMATS.keys()),
'video_upload_max_file_size': VIDEO_UPLOAD_MAX_FILE_SIZE_GB,
'video_image_settings': {
'video_image_upload_enabled': VIDEO_IMAGE_UPLOAD_ENABLED.is_enabled(),
'max_size': settings.VIDEO_IMAGE_SETTINGS['VIDEO_IMAGE_MAX_BYTES'],
'min_size': settings.VIDEO_IMAGE_SETTINGS['VIDEO_IMAGE_MIN_BYTES'],
'max_width': settings.VIDEO_IMAGE_MAX_WIDTH,
'max_height': settings.VIDEO_IMAGE_MAX_HEIGHT,
'supported_file_formats': settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS
},
'is_video_transcript_enabled': is_video_transcript_enabled,
'active_transcript_preferences': None,
'transcript_credentials': None,
'transcript_available_languages': get_all_transcript_languages(),
'video_transcript_settings': {
'transcript_download_handler_url': reverse('transcript_download_handler'),
'transcript_upload_handler_url': reverse('transcript_upload_handler'),
'transcript_delete_handler_url': reverse_course_url('transcript_delete_handler', str(course.id)),
'trancript_download_file_format': Transcript.SRT
},
'pagination_context': pagination_context
}
if is_video_transcript_enabled:
context['video_transcript_settings'].update({
'transcript_preferences_handler_url': reverse_course_url(
'transcript_preferences_handler',
str(course.id)
),
'transcript_credentials_handler_url': reverse_course_url(
'transcript_credentials_handler',
str(course.id)
),
'transcription_plans': get_3rd_party_transcription_plans(),
})
context['active_transcript_preferences'] = get_transcript_preferences(str(course.id))
# Cached state for transcript providers' credentials (org-specific)
context['transcript_credentials'] = get_transcript_credentials_state_for_org(course.id.org)
context = get_course_videos_context(
course,
pagination_conf,
)
if use_new_video_uploads_page(course.id):
return redirect(get_video_uploads_url(course.id))
return render_to_response('videos_index.html', context)

View File

@@ -358,6 +358,7 @@ class VideosHandlerTestCase(
for i, response_video in enumerate(response_videos):
# Videos should be returned by creation date descending
original_video = self.previous_uploads[-(i + 1)]
print(response_video.keys())
self.assertEqual(
set(response_video.keys()),
{
@@ -367,6 +368,8 @@ class VideosHandlerTestCase(
'duration',
'status',
'course_video_image_url',
'file_size',
'download_link',
'transcripts',
'transcription_status',
'transcript_urls',
@@ -385,8 +388,8 @@ class VideosHandlerTestCase(
(
[
'edx_video_id', 'client_video_id', 'created', 'duration',
'status', 'course_video_image_url', 'transcripts', 'transcription_status',
'transcript_urls', 'error_description'
'status', 'course_video_image_url', 'file_size', 'download_link',
'transcripts', 'transcription_status', 'transcript_urls', 'error_description'
],
[
{
@@ -402,8 +405,8 @@ class VideosHandlerTestCase(
(
[
'edx_video_id', 'client_video_id', 'created', 'duration',
'status', 'course_video_image_url', 'transcripts', 'transcription_status',
'transcript_urls', 'error_description'
'status', 'course_video_image_url', 'file_size', 'download_link',
'transcripts', 'transcription_status', 'transcript_urls', 'error_description'
],
[
{
@@ -444,8 +447,9 @@ class VideosHandlerTestCase(
self.assertEqual(response.status_code, 200)
response_videos = json.loads(response.content.decode('utf-8'))['videos']
self.assertEqual(len(response_videos), len(self.previous_uploads))
for response_video in response_videos:
print(response_video)
self.assertEqual(set(response_video.keys()), set(expected_video_keys))
if response_video['edx_video_id'] == self.previous_uploads[0]['edx_video_id']:
self.assertEqual(response_video.get('transcripts', []), expected_transcripts)