diff --git a/edxval/tests/constants.py b/edxval/tests/constants.py index 500c2dbd..53168e5c 100644 --- a/edxval/tests/constants.py +++ b/edxval/tests/constants.py @@ -386,6 +386,13 @@ status="test", ) +VIDEO_DICT_SIMPSONS = dict( + client_video_id="TheSimpsons", + duration=100.00, + edx_video_id="simpson-id", + status="test", +) + TRANSCRIPT_DATA = { "overwatch": """ 1 @@ -452,3 +459,30 @@ preferred_languages=['ar', 'en'], video_source_language='en', ) + +VIDEO_TRANSCRIPT_SIMPSON_ES = dict( + video_id='simpson-id', + language_code='es', + transcript='edxval/tests/data/The_Flash.srt', + provider=TranscriptProviderType.CIELO24, + file_format=TranscriptFormat.SRT, + file_data=TRANSCRIPT_DATA['flash'] +) + +VIDEO_TRANSCRIPT_SIMPSON_KO = dict( + video_id='simpson-id', + language_code='ko', + transcript='edxval/tests/data/The_Flash.srt', + provider=TranscriptProviderType.CIELO24, + file_format=TranscriptFormat.SRT, + file_data=TRANSCRIPT_DATA['flash'] +) + +VIDEO_TRANSCRIPT_SIMPSON_RU = dict( + video_id='simpson-id', + language_code='ru', + transcript='edxval/tests/data/The_Flash.srt', + provider=TranscriptProviderType.CIELO24, + file_format=TranscriptFormat.SRT, + file_data=TRANSCRIPT_DATA['flash'] +) diff --git a/edxval/tests/test_views.py b/edxval/tests/test_views.py index 1c2bda2d..6d1b8dd2 100644 --- a/edxval/tests/test_views.py +++ b/edxval/tests/test_views.py @@ -8,7 +8,9 @@ from ddt import data, ddt, unpack from django.urls import reverse +from edx_rest_framework_extensions.permissions import IsStaff from rest_framework import status +from rest_framework.permissions import IsAuthenticated from edxval.models import CourseVideo, EncodedVideo, Profile, TranscriptProviderType, Video, VideoTranscript from edxval.serializers import TranscriptSerializer @@ -1152,3 +1154,70 @@ def test_successful_response(self): mock_video_ids.assert_called_once_with(course_id) self.assertEqual(response.status_code, status.HTTP_200_OK) + + +@ddt +class VideoTranscriptDeleteTest(APIAuthTestCase): + """ + Tests for transcript bulk deletion handler. + """ + def setUp(self): + """ + Tests setup. + """ + self.url = reverse('video-transcripts') + self.patcher = patch.object(IsAuthenticated, "has_permission", return_value=True) + self.patcher = patch.object(IsStaff, "has_permission", return_value=True) + self.patcher.start() + + self.video_1 = Video.objects.create(**constants.VIDEO_DICT_SIMPSONS) + self.transcript_data_es = constants.VIDEO_TRANSCRIPT_SIMPSON_ES + self.transcript_data_ko = constants.VIDEO_TRANSCRIPT_SIMPSON_KO + self.transcript_data_ru = constants.VIDEO_TRANSCRIPT_SIMPSON_RU + super().setUp() + + def tearDown(self): + self.patcher.stop() + + def test_transcript_fail_authorized(self): + with patch.object(IsAuthenticated, "has_permission", return_value=False): + response = self.client.delete(self.url) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_transcript_delete_fail_no_staff(self): + with patch.object(IsStaff, "has_permission", return_value=False): + response = self.client.delete(self.url) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_transcript_delete_success(self): + VideoTranscript.objects.create( + video=self.video_1, + language_code=self.transcript_data_es['language_code'], + file_format=self.transcript_data_es['file_format'], + provider=self.transcript_data_es['provider'], + ) + VideoTranscript.objects.create( + video=self.video_1, + language_code=self.transcript_data_ko['language_code'], + file_format=self.transcript_data_ko['file_format'], + provider=self.transcript_data_ko['provider'], + ) + VideoTranscript.objects.create( + video=self.video_1, + language_code=self.transcript_data_ru['language_code'], + file_format=self.transcript_data_ru['file_format'], + provider=self.transcript_data_ru['provider'], + ) + + response1 = self.client.delete(f'{self.url}?video_id=simpson-id&language_code=es') + self.assertEqual(response1.status_code, status.HTTP_204_NO_CONTENT) + + response2 = self.client.delete(f'{self.url}?video_id=simpson-id&language_code=ko') + self.assertEqual(response2.status_code, status.HTTP_204_NO_CONTENT) + + response3 = self.client.delete(f'{self.url}?video_id=simpson-id&language_code=ru') + self.assertEqual(response3.status_code, status.HTTP_204_NO_CONTENT) + + def test_transcript_delete_fail_bad_request(self): + response = self.client.delete(self.url) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) diff --git a/edxval/urls.py b/edxval/urls.py index 97a05d18..d985746d 100644 --- a/edxval/urls.py +++ b/edxval/urls.py @@ -25,6 +25,9 @@ path('videos/video-transcripts/create/', views.VideoTranscriptView.as_view(), name='create-video-transcript' ), + path('videos/video-transcripts/', views.VideoTranscriptView.as_view(), + name='video-transcripts' + ), path('videos/video-images/update/', views.VideoImagesView.as_view(), name='update-video-images' ), diff --git a/edxval/views.py b/edxval/views.py index f382216a..0a340451 100644 --- a/edxval/views.py +++ b/edxval/views.py @@ -8,13 +8,19 @@ from django.core.exceptions import ValidationError from django.shortcuts import get_object_or_404 from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication +from edx_rest_framework_extensions.permissions import IsStaff from rest_framework import generics, status from rest_framework.authentication import SessionAuthentication -from rest_framework.permissions import DjangoModelPermissions +from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView -from edxval.api import create_or_update_video_transcript, get_transcript_details_for_course, get_video_ids_for_course +from edxval.api import ( + create_or_update_video_transcript, + delete_video_transcript, + get_transcript_details_for_course, + get_video_ids_for_course, +) from edxval.models import ( LIST_MAX_ITEMS, CourseVideo, @@ -119,7 +125,11 @@ class VideoTranscriptView(APIView): """ authentication_classes = (JwtAuthentication, SessionAuthentication) - # noinspection PyMethodMayBeStatic + def get_permissions(self): + if self.request.method == 'DELETE': + return [IsAuthenticated(), IsStaff()] + return [] + def post(self, request): """ Creates a video transcript instance with the given information. @@ -174,6 +184,39 @@ def post(self, request): return response + def delete(self, request): + """ + Delete a video transcript instance with the given information. + + Arguments: + request: A WSGI request. + """ + params = ('video_id', 'language_code') + missing = [param for param in params if param not in request.query_params] + if missing: + LOGGER.warning( + '[VAL] Required transcript params are missing. %s', ' and '.join(missing) + ) + return Response( + status=status.HTTP_400_BAD_REQUEST, + data=dict(message='{missing} must be specified.'.format(missing=' and '.join(missing))) + ) + + video_id = request.query_params.get('video_id') + language_code = request.query_params.get('language_code') + + try: + delete_video_transcript(video_id=video_id, language_code=language_code) + except Exception as e: # pylint: disable=broad-exception-caught + return Response( + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + data={'message': str(e)} + ) + + return Response( + status=status.HTTP_204_NO_CONTENT, + ) + class CourseTranscriptsDetailView(APIView): """