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:
@@ -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'
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
62
cms/djangoapps/contentstore/rest_api/v1/views/transcripts.py
Normal file
62
cms/djangoapps/contentstore/rest_api/v1/views/transcripts.py
Normal 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)
|
||||
159
cms/djangoapps/contentstore/rest_api/v1/views/videos.py
Normal file
159
cms/djangoapps/contentstore/rest_api/v1/views/videos.py
Normal 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())
|
||||
@@ -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>"
|
||||
|
||||
Reference in New Issue
Block a user