Studio content api videos (#32803)

* refactor: extract methods to video_storage_handlers

* refactor: move private functions

* refactor: move functions to videos_storage_handlers

* refactor: asset_storage_handlers

* feat: add video api views

* feat: add video urls

* feat: add mock videos post

* refactor: mock video upload url

* fix: json extraction

* fix: url pattern for video deletion

* fix: video url views

* fix: lint

* fix: lint

* fix: tests

* fix: tests

* fix: tests

* Feat  studio content api transcripts (#32858)

* feat: add transcript endpoints

feat: add transcript upload endpoint, check that transcripts for deletion exist

fix: remove transcript credentials view cause out of scope

* fix: lint

* feat: TNL-10897 fix destroy() args to kwargs bug

---------

Co-authored-by: Bernard Szabo <bszabo@edx.org>

---------

Co-authored-by: Bernard Szabo <bszabo@edx.org>
This commit is contained in:
Jesper Hodge
2023-07-31 13:37:00 -04:00
committed by GitHub
parent 96699d577c
commit 6598abbb6b
15 changed files with 1551 additions and 959 deletions

View File

@@ -13,10 +13,14 @@ from .views import (
ProctoringErrorsView,
xblock,
assets,
videos,
transcripts,
)
app_name = 'v1'
VIDEO_ID_PATTERN = r'(?:(?P<edx_video_id>[-\w]+))'
urlpatterns = [
re_path(
fr'^proctored_exam_settings/{COURSE_ID_PATTERN}$',
@@ -51,4 +55,28 @@ urlpatterns = [
fr'^file_assets/{settings.COURSE_ID_PATTERN}/{settings.ASSET_KEY_PATTERN}?$',
assets.AssetsView.as_view(), name='studio_content_assets'
),
re_path(
fr'^videos/uploads/{settings.COURSE_ID_PATTERN}/{VIDEO_ID_PATTERN}?$',
videos.VideosView.as_view(), name='studio_content_videos_uploads'
),
re_path(
fr'^videos/images/{settings.COURSE_ID_PATTERN}/{VIDEO_ID_PATTERN}?$',
videos.VideoImagesView.as_view(), name='studio_content_videos_images'
),
re_path(
fr'^videos/encodings/{settings.COURSE_ID_PATTERN}$',
videos.VideoEncodingsDownloadView.as_view(), name='studio_content_videos_encodings'
),
re_path(
r'^videos/features/$',
videos.VideoFeaturesView.as_view(), name='studio_content_videos_features'
),
re_path(
fr'^videos/upload_link/{settings.COURSE_ID_PATTERN}$',
videos.UploadLinkView.as_view(), name='studio_content_videos_upload_link'
),
re_path(
fr'^video_transcripts/{settings.COURSE_ID_PATTERN}$',
transcripts.TranscriptView.as_view(), name='studio_content_video_transcripts'
),
]

View File

@@ -6,3 +6,5 @@ from .grading import CourseGradingView
from .proctoring import ProctoredExamSettingsView, ProctoringErrorsView
from .settings import CourseSettingsView
from .xblock import XblockView
from .assets import AssetsView
from .videos import VideosView

View File

@@ -1,4 +1,6 @@
# lint-amnesty, pylint: disable=missing-module-docstring
"""
Public rest API endpoints for the Studio Content API Assets.
"""
import logging
from rest_framework.generics import RetrieveUpdateDestroyAPIView, CreateAPIView
from django.views.decorators.csrf import csrf_exempt
@@ -19,7 +21,7 @@ toggles = contentstore_toggles
@view_auth_classes()
class AssetsView(DeveloperErrorViewMixin, RetrieveUpdateDestroyAPIView, CreateAPIView):
"""
public rest API endpoint for the Studio Content API.
public rest API endpoints for the Studio Content API Assets.
course_key: required argument, needed to authorize course authors and identify the asset.
asset_key_string: required argument, needed to identify the asset.
"""

View File

@@ -0,0 +1,62 @@
"""
Public rest API endpoints for the Studio Content API video assets.
"""
import logging
from rest_framework.generics import (
CreateAPIView,
RetrieveAPIView,
DestroyAPIView
)
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 common.djangoapps.util.json_request import expect_json_in_class_view
from ....api import course_author_access_required
from cms.djangoapps.contentstore.transcript_storage_handlers import (
upload_transcript,
delete_video_transcript_or_404,
handle_transcript_download,
)
import cms.djangoapps.contentstore.toggles as contentstore_toggles
log = logging.getLogger(__name__)
toggles = contentstore_toggles
@view_auth_classes()
class TranscriptView(DeveloperErrorViewMixin, CreateAPIView, RetrieveAPIView, DestroyAPIView):
"""
public rest API endpoints for the Studio Content API video transcripts.
course_key: required argument, needed to authorize course authors and identify the video.
edx_video_id: optional query parameter, needed to identify the transcript.
language_code: optional query parameter, needed to identify the transcript.
"""
def dispatch(self, request, *args, **kwargs):
if not toggles.use_studio_content_api():
raise Http404
return super().dispatch(request, *args, **kwargs)
@csrf_exempt
@course_author_access_required
@expect_json_in_class_view
def create(self, request, course_key_string): # pylint: disable=arguments-differ
return upload_transcript(request)
@course_author_access_required
def retrieve(self, request, course_key_string): # pylint: disable=arguments-differ
"""
Get a video transcript. edx_video_id and language_code query parameters are required.
"""
return handle_transcript_download(request)
@course_author_access_required
def destroy(self, request, course_key_string): # pylint: disable=arguments-differ
"""
Delete a video transcript. edx_video_id and language_code query parameters are required.
"""
return delete_video_transcript_or_404(request)

View File

@@ -0,0 +1,159 @@
"""
Public rest API endpoints for the Studio Content API video assets.
"""
import logging
from rest_framework.generics import (
CreateAPIView,
RetrieveAPIView,
DestroyAPIView
)
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 common.djangoapps.util.json_request import expect_json_in_class_view
from ....api import course_author_access_required
from cms.djangoapps.contentstore.video_storage_handlers import (
handle_videos,
get_video_encodings_download,
handle_video_images,
enabled_video_features,
handle_generate_video_upload_link
)
import cms.djangoapps.contentstore.toggles as contentstore_toggles
log = logging.getLogger(__name__)
toggles = contentstore_toggles
@view_auth_classes()
class VideosView(DeveloperErrorViewMixin, CreateAPIView, RetrieveAPIView, DestroyAPIView):
"""
public rest API endpoints for the Studio Content API video assets.
course_key: required argument, needed to authorize course authors and identify the video.
video_id: required argument, needed to identify the video.
"""
def dispatch(self, request, *args, **kwargs):
# TODO: probably want to refactor this to a decorator.
"""
The dispatch method of a View class handles HTTP requests in general
and calls other methods to handle specific HTTP methods.
We use this to raise a 404 if the content api is disabled.
"""
if not toggles.use_studio_content_api():
raise Http404
return super().dispatch(request, *args, **kwargs)
@csrf_exempt
@course_author_access_required
@expect_json_in_class_view
def create(self, request, course_key): # pylint: disable=arguments-differ
return handle_videos(request, course_key.html_id())
@course_author_access_required
def retrieve(self, request, course_key, edx_video_id=None): # pylint: disable=arguments-differ
return handle_videos(request, course_key.html_id(), edx_video_id)
@course_author_access_required
@expect_json_in_class_view
def destroy(self, request, course_key, edx_video_id): # pylint: disable=arguments-differ
return handle_videos(request, course_key.html_id(), edx_video_id)
@view_auth_classes()
class VideoImagesView(DeveloperErrorViewMixin, CreateAPIView):
"""
public rest API endpoint for uploading a video image.
course_key: required argument, needed to authorize course authors and identify the video.
video_id: required argument, needed to identify the video.
"""
def dispatch(self, request, *args, **kwargs):
# TODO: probably want to refactor this to a decorator.
"""
The dispatch method of a View class handles HTTP requests in general
and calls other methods to handle specific HTTP methods.
We use this to raise a 404 if the content api is disabled.
"""
if not toggles.use_studio_content_api():
raise Http404
return super().dispatch(request, *args, **kwargs)
@csrf_exempt
@course_author_access_required
@expect_json_in_class_view
def create(self, request, course_key, edx_video_id=None): # pylint: disable=arguments-differ
return handle_video_images(request, course_key.html_id(), edx_video_id)
@view_auth_classes()
class VideoEncodingsDownloadView(DeveloperErrorViewMixin, RetrieveAPIView):
"""
public rest API endpoint providing a CSV report containing the encoded video URLs for video uploads.
course_key: required argument, needed to authorize course authors and identify relevant videos.
"""
def dispatch(self, request, *args, **kwargs):
# TODO: probably want to refactor this to a decorator.
"""
The dispatch method of a View class handles HTTP requests in general
and calls other methods to handle specific HTTP methods.
We use this to raise a 404 if the content api is disabled.
"""
if not toggles.use_studio_content_api():
raise Http404
return super().dispatch(request, *args, **kwargs)
@csrf_exempt
@course_author_access_required
def retrieve(self, request, course_key): # pylint: disable=arguments-differ
return get_video_encodings_download(request, course_key.html_id())
@view_auth_classes()
class VideoFeaturesView(DeveloperErrorViewMixin, RetrieveAPIView):
"""
public rest API endpoint providing a list of enabled video features.
"""
def dispatch(self, request, *args, **kwargs):
# TODO: probably want to refactor this to a decorator.
"""
The dispatch method of a View class handles HTTP requests in general
and calls other methods to handle specific HTTP methods.
We use this to raise a 404 if the content api is disabled.
"""
if not toggles.use_studio_content_api():
raise Http404
return super().dispatch(request, *args, **kwargs)
@csrf_exempt
def retrieve(self, request): # pylint: disable=arguments-differ
return enabled_video_features(request)
@view_auth_classes()
class UploadLinkView(DeveloperErrorViewMixin, CreateAPIView):
"""
public rest API endpoint providing a list of enabled video features.
"""
def dispatch(self, request, *args, **kwargs):
# TODO: probably want to refactor this to a decorator.
"""
The dispatch method of a View class handles HTTP requests in general
and calls other methods to handle specific HTTP methods.
We use this to raise a 404 if the content api is disabled.
"""
if not toggles.use_studio_content_api():
raise Http404
return super().dispatch(request, *args, **kwargs)
@csrf_exempt
@course_author_access_required
@expect_json_in_class_view
def create(self, request, course_key): # pylint: disable=arguments-differ
return handle_generate_video_upload_link(request, course_key.html_id())

View File

@@ -1,4 +1,6 @@
# lint-amnesty, pylint: disable=missing-module-docstring
"""
Public rest API endpoints for the Studio Content API.
"""
import logging
from rest_framework.generics import RetrieveUpdateDestroyAPIView, CreateAPIView
from django.views.decorators.csrf import csrf_exempt
@@ -20,7 +22,7 @@ handle_xblock = view_handlers.handle_xblock
@view_auth_classes()
class XblockView(DeveloperErrorViewMixin, RetrieveUpdateDestroyAPIView, CreateAPIView):
"""
public rest API endpoint for the Studio Content API.
Public rest API endpoints for the Studio Content API.
course_key: required argument, needed to authorize course authors.
usage_key_string (optional):
xblock identifier, for example in the form of "block-v1:<course id>+type@<type>+block@<block id>"