Skip to content

Commit

Permalink
Merge branch 'master' into jkantor/transcript-provider-choice
Browse files Browse the repository at this point in the history
  • Loading branch information
jansenk authored Dec 12, 2024
2 parents 328710c + 189ec0a commit 33a3c71
Show file tree
Hide file tree
Showing 10 changed files with 215 additions and 64 deletions.
34 changes: 34 additions & 0 deletions edxval/tests/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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']
)
69 changes: 69 additions & 0 deletions edxval/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
3 changes: 3 additions & 0 deletions edxval/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
),
Expand Down
49 changes: 46 additions & 3 deletions edxval/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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):
"""
Expand Down
4 changes: 2 additions & 2 deletions requirements/ci.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ charset-normalizer==3.4.0
# via requests
colorama==0.4.6
# via tox
coverage[toml]==7.6.7
coverage[toml]==7.6.8
# via coveralls
coveralls==4.0.1
# via -r requirements/ci.in
Expand Down Expand Up @@ -46,5 +46,5 @@ tox==4.23.2
# via -r requirements/ci.in
urllib3==2.2.3
# via requests
virtualenv==20.27.1
virtualenv==20.28.0
# via tox
44 changes: 22 additions & 22 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#
annotated-types==0.7.0
# via pydantic
anyio==4.6.2.post1
anyio==4.7.0
# via starlette
appdirs==1.4.4
# via fs
Expand Down Expand Up @@ -44,20 +44,20 @@ click==8.1.7
# uvicorn
click-log==0.4.0
# via edx-lint
code-annotations==1.8.2
code-annotations==2.0.0
# via
# edx-lint
# edx-toggles
colorama==0.4.6
# via tox
coverage[toml]==7.6.7
coverage[toml]==7.6.8
# via
# -r requirements/test.in
# coveralls
# pytest-cov
coveralls==4.0.1
# via -r requirements/ci.in
cryptography==43.0.3
cryptography==44.0.0
# via
# pyjwt
# secretstorage
Expand All @@ -69,7 +69,7 @@ dill==0.3.9
# via pylint
distlib==0.3.9
# via virtualenv
django==4.2.16
django==4.2.17
# via
# -c requirements/common_constraints.txt
# -r requirements/base.in
Expand Down Expand Up @@ -110,7 +110,7 @@ drf-jwt==1.19.2
# via edx-drf-extensions
edx-django-release-util==1.4.0
# via -r requirements/base.in
edx-django-utils==7.0.1
edx-django-utils==7.1.0
# via
# edx-drf-extensions
# edx-toggles
Expand All @@ -124,7 +124,7 @@ edx-opaque-keys==2.11.0
# via edx-drf-extensions
edx-toggles==5.2.0
# via -r requirements/base.in
fastapi==0.115.5
fastapi==0.115.6
# via pact-python
filelock==3.16.1
# via
Expand All @@ -140,9 +140,7 @@ idna==3.10
# requests
# yarl
importlib-metadata==8.5.0
# via
# keyring
# twine
# via keyring
iniconfig==2.0.0
# via pytest
isort==5.13.2
Expand Down Expand Up @@ -183,22 +181,23 @@ more-itertools==10.5.0
# jaraco-functools
multidict==6.1.0
# via yarl
newrelic==10.3.0
newrelic==10.3.1
# via edx-django-utils
nh3==0.2.18
nh3==0.2.19
# via readme-renderer
packaging==24.2
# via
# pyproject-api
# pytest
# tox
# twine
pact-python==2.2.2
# via -r requirements/test.in
pbr==6.1.0
# via stevedore
pillow==11.0.0
# via -r requirements/base.in
pkginfo==1.10.0
pkginfo==1.12.0
# via twine
platformdirs==4.3.6
# via
Expand All @@ -210,7 +209,7 @@ pluggy==1.5.0
# diff-cover
# pytest
# tox
propcache==0.2.0
propcache==0.2.1
# via yarl
psutil==6.1.0
# via
Expand All @@ -220,7 +219,7 @@ pycodestyle==2.12.1
# via -r requirements/quality.in
pycparser==2.22
# via cffi
pydantic==2.10.1
pydantic==2.10.3
# via fastapi
pydantic-core==2.27.1
# via pydantic
Expand All @@ -231,11 +230,11 @@ pygments==2.18.0
# diff-cover
# readme-renderer
# rich
pyjwt[crypto]==2.10.0
pyjwt[crypto]==2.10.1
# via
# drf-jwt
# edx-drf-extensions
pylint==3.3.1
pylint==3.3.2
# via
# edx-lint
# pylint-celery
Expand All @@ -257,7 +256,7 @@ pyproject-api==1.8.0
# via tox
pysrt==1.1.2
# via -r requirements/base.in
pytest==8.3.3
pytest==8.3.4
# via
# pytest-cov
# pytest-django
Expand Down Expand Up @@ -294,7 +293,7 @@ secretstorage==3.3.3
# via keyring
semantic-version==2.10.0
# via edx-drf-extensions
six==1.16.0
six==1.17.0
# via
# edx-django-release-util
# edx-lint
Expand All @@ -319,10 +318,11 @@ tomlkit==0.13.2
# via pylint
tox==4.23.2
# via -r requirements/ci.in
twine==5.1.1
twine==6.0.1
# via -r requirements/quality.in
typing-extensions==4.12.2
# via
# anyio
# edx-opaque-keys
# fastapi
# pydantic
Expand All @@ -334,9 +334,9 @@ urllib3==2.2.3
# twine
uvicorn==0.32.1
# via pact-python
virtualenv==20.27.1
virtualenv==20.28.0
# via tox
yarl==1.18.0
yarl==1.18.3
# via pact-python
zipp==3.21.0
# via importlib-metadata
Expand Down
Loading

0 comments on commit 33a3c71

Please sign in to comment.