feat: TNL-11173 Authoring API is v0 for now (#33644)
* feat: TNL-11173 authoring API offered as v0, not v1 * docs: correct swaggerfile for authoring api --------- Co-authored-by: Bernard Szabo <bszabo@edx.org> Co-authored-by: Jesper Hodge <jhodge@outlook.de>
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
"""
|
||||
Serializers for all contentstore API versions
|
||||
"""
|
||||
from .common import StrictSerializer
|
||||
@@ -0,0 +1,12 @@
|
||||
"""
|
||||
Views for v0 contentstore API.
|
||||
"""
|
||||
|
||||
from cms.djangoapps.contentstore.rest_api.v0.views.assets import (
|
||||
AssetsCreateRetrieveView,
|
||||
AssetsUpdateDestroyView
|
||||
)
|
||||
from cms.djangoapps.contentstore.rest_api.v0.views.xblock import (
|
||||
XblockView,
|
||||
XblockCreateView
|
||||
)
|
||||
|
||||
@@ -2,4 +2,7 @@
|
||||
Serializers for v0 contentstore API.
|
||||
"""
|
||||
from .advanced_settings import AdvancedSettingsFieldSerializer, CourseAdvancedSettingsSerializer
|
||||
from .assets import AssetSerializer
|
||||
from .tabs import CourseTabSerializer, CourseTabUpdateSerializer, TabIDLocatorSerializer
|
||||
from .transcripts import TranscriptSerializer
|
||||
from .xblock import XblockSerializer
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
API Serializers for assets
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
from .common import StrictSerializer
|
||||
from cms.djangoapps.contentstore.rest_api.serializers.common import StrictSerializer
|
||||
|
||||
|
||||
class AssetSerializer(StrictSerializer):
|
||||
@@ -2,7 +2,7 @@
|
||||
API Serializers for transcripts
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
from .common import StrictSerializer
|
||||
from cms.djangoapps.contentstore.rest_api.serializers.common import StrictSerializer
|
||||
|
||||
|
||||
class TranscriptSerializer(StrictSerializer):
|
||||
@@ -2,7 +2,7 @@
|
||||
API Serializers for xblocks
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
from .common import StrictSerializer
|
||||
from cms.djangoapps.contentstore.rest_api.serializers.common import StrictSerializer
|
||||
|
||||
# The XblockSerializer is designed to be scalable and generic. As such, its structure
|
||||
# should remain as general as possible. Avoid indiscriminately adding fields to it,
|
||||
@@ -21,6 +21,7 @@ from cms.djangoapps.contentstore.tests.test_utils import AuthorizeStaffTestCase
|
||||
ASSET_KEY_STRING = "asset-v1:dede+aba+weagi+type@asset+block@_0e37192a-42c4-441e-a3e1-8e40ec304e2e.jpg"
|
||||
mock_image = MagicMock(file=File)
|
||||
mock_image.name = "test.jpg"
|
||||
VERSION = "v0"
|
||||
|
||||
|
||||
class AssetsViewTestCase(AuthorizeStaffTestCase):
|
||||
@@ -44,7 +45,7 @@ class AssetsViewTestCase(AuthorizeStaffTestCase):
|
||||
|
||||
def get_url(self, _course_id=None):
|
||||
return reverse(
|
||||
"cms.djangoapps.contentstore:v1:cms_api_update_destroy_assets",
|
||||
f"cms.djangoapps.contentstore:{VERSION}:cms_api_update_destroy_assets",
|
||||
kwargs=self.get_url_params(),
|
||||
)
|
||||
|
||||
@@ -52,7 +53,7 @@ class AssetsViewTestCase(AuthorizeStaffTestCase):
|
||||
raise NotImplementedError("send_request must be implemented by subclasses")
|
||||
|
||||
@patch(
|
||||
"cms.djangoapps.contentstore.rest_api.v1.views.assets.handle_assets",
|
||||
f"cms.djangoapps.contentstore.rest_api.{VERSION}.views.assets.handle_assets",
|
||||
return_value=JsonResponse(
|
||||
{
|
||||
"locator": ASSET_KEY_STRING,
|
||||
@@ -61,7 +62,7 @@ class AssetsViewTestCase(AuthorizeStaffTestCase):
|
||||
),
|
||||
)
|
||||
@patch(
|
||||
"cms.djangoapps.contentstore.rest_api.v1.views.xblock.toggles.use_studio_content_api",
|
||||
f"cms.djangoapps.contentstore.rest_api.{VERSION}.views.xblock.toggles.use_studio_content_api",
|
||||
return_value=True,
|
||||
)
|
||||
def make_request(
|
||||
@@ -104,7 +105,7 @@ class AssetsViewGetTest(AssetsViewTestCase, ModuleStoreTestCase, APITestCase):
|
||||
|
||||
def get_url(self, _course_id=None):
|
||||
return reverse(
|
||||
"cms.djangoapps.contentstore:v1:cms_api_create_retrieve_assets",
|
||||
f"cms.djangoapps.contentstore:{VERSION}:cms_api_create_retrieve_assets",
|
||||
kwargs=self.get_url_params(),
|
||||
)
|
||||
|
||||
@@ -156,7 +157,7 @@ class AssetsViewPostTest(AssetsViewTestCase, ModuleStoreTestCase, APITestCase):
|
||||
|
||||
def get_url(self, _course_id=None):
|
||||
return reverse(
|
||||
"cms.djangoapps.contentstore:v1:cms_api_create_retrieve_assets",
|
||||
f"cms.djangoapps.contentstore:{VERSION}:cms_api_create_retrieve_assets",
|
||||
kwargs=self.get_url_params(),
|
||||
)
|
||||
|
||||
@@ -15,6 +15,7 @@ from cms.djangoapps.contentstore.tests.test_utils import AuthorizeStaffTestCase
|
||||
|
||||
|
||||
TEST_LOCATOR = "block-v1:dede+aba+weagi+type@problem+block@ba6327f840da49289fb27a9243913478"
|
||||
VERSION = "v0"
|
||||
|
||||
|
||||
class XBlockViewTestCase(AuthorizeStaffTestCase):
|
||||
@@ -38,7 +39,7 @@ class XBlockViewTestCase(AuthorizeStaffTestCase):
|
||||
|
||||
def get_url(self, _course_id=None):
|
||||
return reverse(
|
||||
"cms.djangoapps.contentstore:v1:cms_api_xblock",
|
||||
f"cms.djangoapps.contentstore:{VERSION}:cms_api_xblock",
|
||||
kwargs=self.get_url_params(),
|
||||
)
|
||||
|
||||
@@ -46,7 +47,7 @@ class XBlockViewTestCase(AuthorizeStaffTestCase):
|
||||
raise NotImplementedError("send_request must be implemented by subclasses")
|
||||
|
||||
@patch(
|
||||
"cms.djangoapps.contentstore.rest_api.v1.views.xblock.handle_xblock",
|
||||
f"cms.djangoapps.contentstore.rest_api.{VERSION}.views.xblock.handle_xblock",
|
||||
return_value=JsonResponse(
|
||||
{
|
||||
"locator": TEST_LOCATOR,
|
||||
@@ -55,7 +56,7 @@ class XBlockViewTestCase(AuthorizeStaffTestCase):
|
||||
),
|
||||
)
|
||||
@patch(
|
||||
"cms.djangoapps.contentstore.rest_api.v1.views.xblock.toggles.use_studio_content_api",
|
||||
f"cms.djangoapps.contentstore.rest_api.{VERSION}.views.xblock.toggles.use_studio_content_api",
|
||||
return_value=True,
|
||||
)
|
||||
def make_request(
|
||||
@@ -134,13 +135,14 @@ class XBlockViewPostTest(XBlockViewTestCase, ModuleStoreTestCase, APITestCase):
|
||||
"""
|
||||
Test POST operation on xblocks - Create a new xblock for a parent xblock
|
||||
"""
|
||||
VERSION = "v0"
|
||||
|
||||
def get_url_params(self):
|
||||
return {"course_id": self.get_course_key_string()}
|
||||
|
||||
def get_url(self, _course_id=None):
|
||||
return reverse(
|
||||
"cms.djangoapps.contentstore:v1:cms_api_create_xblock",
|
||||
f"cms.djangoapps.contentstore:{VERSION}:cms_api_create_xblock",
|
||||
kwargs=self.get_url_params(),
|
||||
)
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
""" Contenstore API v0 URLs. """
|
||||
|
||||
from django.urls import re_path
|
||||
from django.conf import settings
|
||||
from django.urls import re_path, path
|
||||
|
||||
from openedx.core.constants import COURSE_ID_PATTERN
|
||||
|
||||
from .views import AdvancedCourseSettingsView, CourseTabSettingsView, CourseTabListView, CourseTabReorderView
|
||||
from .views import assets
|
||||
from .views import transcripts
|
||||
from .views import authoring_videos
|
||||
from .views import xblock
|
||||
|
||||
app_name = "v0"
|
||||
|
||||
VIDEO_ID_PATTERN = r'(?P<edx_video_id>[-\w]+)'
|
||||
|
||||
urlpatterns = [
|
||||
re_path(
|
||||
fr"^advanced_settings/{COURSE_ID_PATTERN}$",
|
||||
@@ -28,4 +36,46 @@ urlpatterns = [
|
||||
CourseTabReorderView.as_view(),
|
||||
name="course_tab_reorder",
|
||||
),
|
||||
|
||||
# Authoring API
|
||||
re_path(
|
||||
fr'^file_assets/{settings.COURSE_ID_PATTERN}/$',
|
||||
assets.AssetsCreateRetrieveView.as_view(), name='cms_api_create_retrieve_assets'
|
||||
),
|
||||
re_path(
|
||||
fr'^file_assets/{settings.COURSE_ID_PATTERN}/{settings.ASSET_KEY_PATTERN}$',
|
||||
assets.AssetsUpdateDestroyView.as_view(), name='cms_api_update_destroy_assets'
|
||||
),
|
||||
re_path(
|
||||
fr'^videos/encodings/{settings.COURSE_ID_PATTERN}$',
|
||||
authoring_videos.VideoEncodingsDownloadView.as_view(), name='cms_api_videos_encodings'
|
||||
),
|
||||
path(
|
||||
'videos/features/',
|
||||
authoring_videos.VideoFeaturesView.as_view(), name='cms_api_videos_features'
|
||||
),
|
||||
re_path(
|
||||
fr'^videos/images/{settings.COURSE_ID_PATTERN}/{VIDEO_ID_PATTERN}$',
|
||||
authoring_videos.VideoImagesView.as_view(), name='cms_api_videos_images'
|
||||
),
|
||||
re_path(
|
||||
fr'^videos/uploads/{settings.COURSE_ID_PATTERN}/$',
|
||||
authoring_videos.VideosCreateUploadView.as_view(), name='cms_api_create_videos_upload'
|
||||
),
|
||||
re_path(
|
||||
fr'^videos/uploads/{settings.COURSE_ID_PATTERN}/{VIDEO_ID_PATTERN}$',
|
||||
authoring_videos.VideosUploadsView.as_view(), name='cms_api_videos_uploads'
|
||||
),
|
||||
re_path(
|
||||
fr'^video_transcripts/{settings.COURSE_ID_PATTERN}$',
|
||||
transcripts.TranscriptView.as_view(), name='cms_api_video_transcripts'
|
||||
),
|
||||
re_path(
|
||||
fr'^xblock/{settings.COURSE_ID_PATTERN}/$',
|
||||
xblock.XblockCreateView.as_view(), name='cms_api_create_xblock'
|
||||
),
|
||||
re_path(
|
||||
fr'^xblock/{settings.COURSE_ID_PATTERN}/{settings.USAGE_KEY_PATTERN}$',
|
||||
xblock.XblockView.as_view(), name='cms_api_xblock'
|
||||
),
|
||||
]
|
||||
|
||||
@@ -9,12 +9,12 @@ 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.api import course_author_access_required
|
||||
|
||||
from cms.djangoapps.contentstore.asset_storage_handlers import handle_assets
|
||||
import cms.djangoapps.contentstore.toggles as contentstore_toggles
|
||||
|
||||
from cms.djangoapps.contentstore.rest_api.v1.serializers import AssetSerializer
|
||||
from ..serializers.assets import AssetSerializer
|
||||
from .utils import validate_request_with_serializer
|
||||
from rest_framework.parsers import (MultiPartParser, FormParser, JSONParser)
|
||||
from openedx.core.lib.api.parsers import TypedFileUploadParser
|
||||
@@ -0,0 +1,167 @@
|
||||
"""
|
||||
Public rest API endpoints for the Authoring API video assets.
|
||||
"""
|
||||
import logging
|
||||
from rest_framework.generics import (
|
||||
CreateAPIView,
|
||||
RetrieveAPIView,
|
||||
DestroyAPIView
|
||||
)
|
||||
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.parsers import TypedFileUploadParser
|
||||
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
|
||||
)
|
||||
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
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
toggles = contentstore_toggles
|
||||
|
||||
|
||||
@view_auth_classes()
|
||||
class VideosUploadsView(DeveloperErrorViewMixin, RetrieveAPIView, DestroyAPIView):
|
||||
"""
|
||||
public rest API endpoints for the CMS 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.
|
||||
"""
|
||||
serializer_class = VideoUploadSerializer
|
||||
|
||||
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)
|
||||
|
||||
@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 VideosCreateUploadView(DeveloperErrorViewMixin, CreateAPIView):
|
||||
"""
|
||||
public rest API endpoints for the CMS API video assets.
|
||||
course_key: required argument, needed to authorize course authors and identify the video.
|
||||
"""
|
||||
serializer_class = VideoUploadSerializer
|
||||
|
||||
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
|
||||
@validate_request_with_serializer
|
||||
def create(self, request, course_key): # pylint: disable=arguments-differ
|
||||
return handle_videos(request, course_key.html_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.
|
||||
"""
|
||||
serializer_class = VideoImageSerializer
|
||||
parser_classes = (MultiPartParser, FormParser, TypedFileUploadParser)
|
||||
|
||||
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
|
||||
@validate_request_with_serializer
|
||||
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)
|
||||
@@ -13,7 +13,7 @@ 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.api import course_author_access_required
|
||||
|
||||
from cms.djangoapps.contentstore.transcript_storage_handlers import (
|
||||
upload_transcript,
|
||||
@@ -21,11 +21,11 @@ from cms.djangoapps.contentstore.transcript_storage_handlers import (
|
||||
handle_transcript_download,
|
||||
)
|
||||
import cms.djangoapps.contentstore.toggles as contentstore_toggles
|
||||
from cms.djangoapps.contentstore.rest_api.v1.serializers import TranscriptSerializer
|
||||
from ..serializers import TranscriptSerializer
|
||||
from rest_framework.parsers import (MultiPartParser, FormParser)
|
||||
from openedx.core.lib.api.parsers import TypedFileUploadParser
|
||||
|
||||
from .utils import validate_request_with_serializer
|
||||
from cms.djangoapps.contentstore.rest_api.v0.views.utils import validate_request_with_serializer
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
toggles = contentstore_toggles
|
||||
@@ -13,7 +13,7 @@ from cms.djangoapps.contentstore.api import course_author_access_required
|
||||
from cms.djangoapps.contentstore.xblock_storage_handlers import view_handlers
|
||||
import cms.djangoapps.contentstore.toggles as contentstore_toggles
|
||||
|
||||
from cms.djangoapps.contentstore.rest_api.v1.serializers import XblockSerializer
|
||||
from ..serializers import XblockSerializer
|
||||
from .utils import validate_request_with_serializer
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"""
|
||||
Serializers for v1 contentstore API.
|
||||
"""
|
||||
from .home import CourseHomeSerializer
|
||||
from .course_details import CourseDetailsSerializer
|
||||
from .course_team import CourseTeamSerializer
|
||||
from .course_rerun import CourseRerunSerializer
|
||||
from .course_team import CourseTeamSerializer
|
||||
from .grading import CourseGradingModelSerializer, CourseGradingSerializer
|
||||
from .home import CourseHomeSerializer
|
||||
from .proctoring import (
|
||||
LimitedProctoredExamSettingsSerializer,
|
||||
ProctoredExamConfigurationSerializer,
|
||||
@@ -13,12 +13,9 @@ from .proctoring import (
|
||||
ProctoringErrorsSerializer
|
||||
)
|
||||
from .settings import CourseSettingsSerializer
|
||||
from .xblock import XblockSerializer
|
||||
from .videos import (
|
||||
CourseVideosSerializer,
|
||||
VideoUploadSerializer,
|
||||
VideoImageSerializer,
|
||||
VideoUsageSerializer
|
||||
)
|
||||
from .transcripts import TranscriptSerializer
|
||||
from .assets import AssetSerializer
|
||||
|
||||
@@ -6,7 +6,7 @@ from rest_framework import serializers
|
||||
|
||||
from openedx.core.lib.api.serializers import CourseKeyField
|
||||
|
||||
from .common import CourseCommonSerializer
|
||||
from cms.djangoapps.contentstore.rest_api.serializers.common import CourseCommonSerializer
|
||||
|
||||
|
||||
class UnsucceededCourseSerializer(serializers.Serializer):
|
||||
|
||||
@@ -4,7 +4,7 @@ API Serializers for course settings
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from .common import CourseCommonSerializer
|
||||
from cms.djangoapps.contentstore.rest_api.serializers.common import CourseCommonSerializer
|
||||
|
||||
|
||||
class CourseSettingsSerializer(serializers.Serializer):
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
API Serializers for videos
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
from .common import StrictSerializer
|
||||
from cms.djangoapps.contentstore.rest_api.serializers.common import StrictSerializer
|
||||
|
||||
|
||||
class FileSpecSerializer(StrictSerializer):
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
""" Contenstore API v1 URLs. """
|
||||
|
||||
from django.conf import settings
|
||||
from django.urls import re_path, path
|
||||
|
||||
from openedx.core.constants import COURSE_ID_PATTERN
|
||||
@@ -15,10 +14,6 @@ from .views import (
|
||||
HomePageView,
|
||||
ProctoredExamSettingsView,
|
||||
ProctoringErrorsView,
|
||||
xblock,
|
||||
assets,
|
||||
videos,
|
||||
transcripts,
|
||||
HelpUrlsView,
|
||||
VideoUsageView
|
||||
)
|
||||
@@ -84,45 +79,6 @@ urlpatterns = [
|
||||
name="course_rerun"
|
||||
),
|
||||
|
||||
# CMS API
|
||||
re_path(
|
||||
fr'^file_assets/{settings.COURSE_ID_PATTERN}/$',
|
||||
assets.AssetsCreateRetrieveView.as_view(), name='cms_api_create_retrieve_assets'
|
||||
),
|
||||
re_path(
|
||||
fr'^file_assets/{settings.COURSE_ID_PATTERN}/{settings.ASSET_KEY_PATTERN}$',
|
||||
assets.AssetsUpdateDestroyView.as_view(), name='cms_api_update_destroy_assets'
|
||||
),
|
||||
re_path(
|
||||
fr'^videos/encodings/{settings.COURSE_ID_PATTERN}$',
|
||||
videos.VideoEncodingsDownloadView.as_view(), name='cms_api_videos_encodings'
|
||||
),
|
||||
path(
|
||||
'videos/features/',
|
||||
videos.VideoFeaturesView.as_view(), name='cms_api_videos_features'
|
||||
),
|
||||
re_path(
|
||||
fr'^videos/images/{settings.COURSE_ID_PATTERN}/{VIDEO_ID_PATTERN}$',
|
||||
videos.VideoImagesView.as_view(), name='cms_api_videos_images'
|
||||
),
|
||||
re_path(
|
||||
fr'^videos/uploads/{settings.COURSE_ID_PATTERN}/$',
|
||||
videos.VideosCreateUploadView.as_view(), name='cms_api_create_videos_upload'
|
||||
),
|
||||
re_path(
|
||||
fr'^videos/uploads/{settings.COURSE_ID_PATTERN}/{VIDEO_ID_PATTERN}$',
|
||||
videos.VideosUploadsView.as_view(), name='cms_api_videos_uploads'
|
||||
),
|
||||
re_path(
|
||||
fr'^video_transcripts/{settings.COURSE_ID_PATTERN}$',
|
||||
transcripts.TranscriptView.as_view(), name='cms_api_video_transcripts'
|
||||
),
|
||||
re_path(
|
||||
fr'^xblock/{settings.COURSE_ID_PATTERN}/$',
|
||||
xblock.XblockCreateView.as_view(), name='cms_api_create_xblock'
|
||||
),
|
||||
re_path(
|
||||
fr'^xblock/{settings.COURSE_ID_PATTERN}/{settings.USAGE_KEY_PATTERN}$',
|
||||
xblock.XblockView.as_view(), name='cms_api_xblock'
|
||||
),
|
||||
# Authoring API
|
||||
# Do not use under v1 yet (Nov. 23). The Authoring API is still experimental and the v0 versions should be used
|
||||
]
|
||||
|
||||
@@ -8,15 +8,8 @@ from .grading import CourseGradingView
|
||||
from .proctoring import ProctoredExamSettingsView, ProctoringErrorsView
|
||||
from .home import HomePageView
|
||||
from .settings import CourseSettingsView
|
||||
from .xblock import XblockView, XblockCreateView
|
||||
from .assets import AssetsCreateRetrieveView, AssetsUpdateDestroyView
|
||||
from .videos import (
|
||||
CourseVideosView,
|
||||
VideosUploadsView,
|
||||
VideosCreateUploadView,
|
||||
VideoImagesView,
|
||||
VideoEncodingsDownloadView,
|
||||
VideoFeaturesView,
|
||||
VideoUsageView,
|
||||
)
|
||||
from .help_urls import HelpUrlsView
|
||||
|
||||
@@ -1,44 +1,26 @@
|
||||
"""
|
||||
Public rest API endpoints for the CMS API video assets.
|
||||
Public rest API endpoints for contentstore API video assets (outside authoring API)
|
||||
"""
|
||||
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, 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,
|
||||
get_video_usage_path
|
||||
)
|
||||
from cms.djangoapps.contentstore.rest_api.v1.serializers import (
|
||||
CourseVideosSerializer,
|
||||
VideoUploadSerializer,
|
||||
VideoImageSerializer,
|
||||
VideoUsageSerializer,
|
||||
)
|
||||
import cms.djangoapps.contentstore.toggles as contentstore_toggles
|
||||
from .utils import validate_request_with_serializer
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -198,135 +180,3 @@ class VideoUsageView(DeveloperErrorViewMixin, APIView):
|
||||
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):
|
||||
"""
|
||||
public rest API endpoints for the CMS 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.
|
||||
"""
|
||||
serializer_class = VideoUploadSerializer
|
||||
|
||||
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)
|
||||
|
||||
@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 VideosCreateUploadView(DeveloperErrorViewMixin, CreateAPIView):
|
||||
"""
|
||||
public rest API endpoints for the CMS API video assets.
|
||||
course_key: required argument, needed to authorize course authors and identify the video.
|
||||
"""
|
||||
serializer_class = VideoUploadSerializer
|
||||
|
||||
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
|
||||
@validate_request_with_serializer
|
||||
def create(self, request, course_key): # pylint: disable=arguments-differ
|
||||
return handle_videos(request, course_key.html_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.
|
||||
"""
|
||||
serializer_class = VideoImageSerializer
|
||||
parser_classes = (MultiPartParser, FormParser, TypedFileUploadParser)
|
||||
|
||||
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
|
||||
@validate_request_with_serializer
|
||||
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)
|
||||
|
||||
@@ -2790,7 +2790,7 @@ SPECTACULAR_SETTINGS = {
|
||||
'DESCRIPTION': 'Experimental API to edit xblocks and course content. Danger: Do not use on running courses!',
|
||||
'VERSION': '1.0.0',
|
||||
'SERVE_INCLUDE_SCHEMA': False,
|
||||
'PREPROCESSING_HOOKS': ['cms.lib.spectacular.cms_api_filter'], # restrict spectacular to CMS API endpoints
|
||||
'PREPROCESSING_HOOKS': ['cms.lib.spectacular.cms_api_filter'], # restrict spectacular to CMS API endpoints. (cms/lib/spectacular.py)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -10,10 +10,12 @@ def cms_api_filter(endpoints):
|
||||
for (path, path_regex, method, callback) in endpoints:
|
||||
# Add only paths to the list that are part of the CMS API
|
||||
if (
|
||||
path.startswith("/api/contentstore/v1/xblock") or
|
||||
path.startswith("/api/contentstore/v1/videos") or
|
||||
path.startswith("/api/contentstore/v1/video_transcripts") or
|
||||
path.startswith("/api/contentstore/v1/file_assets")
|
||||
# Don't just replace this with /v1 when switching to a later version of the CMS API.
|
||||
# That would include some unintended endpoints.
|
||||
path.startswith("/api/contentstore/v0/xblock") or
|
||||
path.startswith("/api/contentstore/v0/videos") or
|
||||
path.startswith("/api/contentstore/v0/video_transcripts") or
|
||||
path.startswith("/api/contentstore/v0/file_assets")
|
||||
):
|
||||
filtered.append((path, path_regex, method, callback))
|
||||
return filtered
|
||||
|
||||
@@ -342,7 +342,10 @@ urlpatterns += [
|
||||
path('api/content_tagging/', include(('openedx.core.djangoapps.content_tagging.urls', 'content_tagging'))),
|
||||
]
|
||||
|
||||
# studio-content-api specific API docs (using drf-spectacular and openapi-v3)
|
||||
# Authoring-api specific API docs (using drf-spectacular and openapi-v3).
|
||||
# This is separate from and in addition to the full studio swagger documentation already existing at /api-docs.
|
||||
# Custom settings are provided in SPECTACULAR_SETTINGS in cms/envs/common.py.
|
||||
# Filter function in cms/lib/spectacular.py determines paths that are swagger-documented.
|
||||
urlpatterns += [
|
||||
re_path('^cms-api/ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
|
||||
re_path('^cms-api/schema/', SpectacularAPIView.as_view(), name='schema'),
|
||||
|
||||
Reference in New Issue
Block a user