Skip to content

Commit aef446c

Browse files
committed
Merge branch 'master' into problemblock-refactor
2 parents 7bd68ce + 360a97f commit aef446c

File tree

9 files changed

+142
-65
lines changed

9 files changed

+142
-65
lines changed

cms/djangoapps/contentstore/rest_api/v2/views/home.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
class HomePageCoursesPaginator(PageNumberPagination):
1717
"""Custom paginator for the home page courses view version 2."""
18+
page_size_query_param = 'page_size'
1819

1920
def get_paginated_response(self, data):
2021
"""Return a paginated style `Response` object for the given output data."""
@@ -77,6 +78,11 @@ class HomePageCoursesViewV2(APIView):
7778
apidocs.ParameterLocation.QUERY,
7879
description="Query param to paginate the courses",
7980
),
81+
apidocs.string_parameter(
82+
"page_size",
83+
apidocs.ParameterLocation.QUERY,
84+
description="Query param to set page size",
85+
),
8086
],
8187
responses={
8288
200: CourseHomeTabSerializerV2,
@@ -96,6 +102,7 @@ def get(self, request: Request):
96102
GET /api/contentstore/v2/home/courses?active_only=true
97103
GET /api/contentstore/v2/home/courses?archived_only=true
98104
GET /api/contentstore/v2/home/courses?page=2
105+
GET /api/contentstore/v2/home/courses?page_size=20
99106
100107
**Response Values**
101108

cms/djangoapps/modulestore_migrator/tasks.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,11 @@ def _populate_collection(user_id: int, migration: models.ModulestoreMigration) -
417417
log.warning("No target entities found to add to collection")
418418

419419

420-
def _create_collection(library_key: LibraryLocatorV2, title: str) -> Collection:
420+
def _create_collection(
421+
library_key: LibraryLocatorV2,
422+
title: str,
423+
course_name: str | None = None,
424+
) -> Collection:
421425
"""
422426
Creates a collection in the given library
423427
@@ -428,7 +432,10 @@ def _create_collection(library_key: LibraryLocatorV2, title: str) -> Collection:
428432
collection: Collection | None = None
429433
attempt = 0
430434
created_at = strftime_localized(datetime.now(timezone.utc), DEFAULT_DATE_TIME_FORMAT)
431-
description = f"{_('This collection contains content migrated from a legacy library on')}: {created_at}"
435+
if course_name:
436+
description = f"{_('This collection contains content imported from the course')} {course_name} on: {created_at}"
437+
else:
438+
description = f"{_('This collection contains content migrated from a legacy library on')}: {created_at}"
432439
while not collection:
433440
modified_key = key if attempt == 0 else key + '-' + str(attempt)
434441
try:
@@ -694,7 +701,11 @@ def bulk_migrate_from_modulestore(
694701
pass
695702
migration.target_collection = (
696703
existing_collection_to_use or
697-
_create_collection(library_key=target_library_locator, title=legacy_root_list[i].display_name)
704+
_create_collection(
705+
library_key=target_library_locator,
706+
title=legacy_root_list[i].display_name,
707+
course_name=legacy_root_list[i].display_name if source_data.source.key.is_course else None,
708+
)
698709
)
699710
_populate_collection(user_id, migration)
700711
models.ModulestoreMigration.objects.bulk_update(

cms/djangoapps/modulestore_migrator/tests/test_tasks.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1118,11 +1118,12 @@ def test_bulk_migrate_create_collections(self):
11181118
"""
11191119
source = ModulestoreSource.objects.create(key=self.legacy_library.location.library_key)
11201120
source_2 = ModulestoreSource.objects.create(key=self.legacy_library_2.location.library_key)
1121+
source_3 = ModulestoreSource.objects.create(key=self.course.id)
11211122

11221123
task = bulk_migrate_from_modulestore.apply_async(
11231124
kwargs={
11241125
"user_id": self.user.id,
1125-
"sources_pks": [source.id, source_2.id],
1126+
"sources_pks": [source.id, source_2.id, source_3.id],
11261127
"target_library_key": str(self.lib_key),
11271128
"target_collection_pks": [],
11281129
"create_collections": True,
@@ -1142,13 +1143,32 @@ def test_bulk_migrate_create_collections(self):
11421143
self.assertEqual(migration.composition_level, CompositionLevel.Unit.value)
11431144
self.assertEqual(migration.repeat_handling_strategy, RepeatHandlingStrategy.Skip.value)
11441145
self.assertEqual(migration.target_collection.title, self.legacy_library.display_name)
1146+
self.assertIn(
1147+
"This collection contains content migrated from a legacy library on:",
1148+
migration.target_collection.description,
1149+
)
11451150

11461151
migration_2 = ModulestoreMigration.objects.get(
11471152
source=source_2, target=self.learning_package
11481153
)
11491154
self.assertEqual(migration_2.composition_level, CompositionLevel.Unit.value)
11501155
self.assertEqual(migration_2.repeat_handling_strategy, RepeatHandlingStrategy.Skip.value)
11511156
self.assertEqual(migration_2.target_collection.title, self.legacy_library_2.display_name)
1157+
self.assertIn(
1158+
"This collection contains content migrated from a legacy library on:",
1159+
migration_2.target_collection.description,
1160+
)
1161+
1162+
migration_3 = ModulestoreMigration.objects.get(
1163+
source=source_3, target=self.learning_package
1164+
)
1165+
self.assertEqual(migration_3.composition_level, CompositionLevel.Unit.value)
1166+
self.assertEqual(migration_3.repeat_handling_strategy, RepeatHandlingStrategy.Skip.value)
1167+
self.assertEqual(migration_3.target_collection.title, self.course.display_name)
1168+
self.assertIn(
1169+
f"This collection contains content imported from the course {self.course.display_name} on:",
1170+
migration_3.target_collection.description,
1171+
)
11521172

11531173
@ddt.data(
11541174
RepeatHandlingStrategy.Skip,

lms/djangoapps/courseware/tests/test_video_handlers.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -953,13 +953,14 @@ class TestStudioTranscriptTranslationPostDispatch(TestVideo): # lint-amnesty, p
953953
"error_message": "A transcript file is required."
954954
},
955955
)
956+
@patch('openedx.core.djangoapps.video_config.services.VideoConfigService.available_translations')
956957
@ddt.unpack
957-
def test_studio_transcript_post_validations(self, post_data, error_message):
958+
def test_studio_transcript_post_validations(self, mock_available_translations, post_data, error_message):
958959
"""
959960
Verify that POST request validations works as expected.
960961
"""
961-
# mock available_translations method
962-
self.block.available_translations = lambda transcripts, verify_assets: ['ur']
962+
# mock available_translations method to return ['ur']
963+
mock_available_translations.return_value = ['ur']
963964
request = Request.blank('/translation', POST=post_data)
964965
response = self.block.studio_transcript(request=request, dispatch='translation')
965966
assert response.json['error'] == error_message

openedx/core/djangoapps/video_config/services.py

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
"""
88

99
import logging
10+
from typing import Any
1011

12+
from django.conf import settings
1113
from opaque_keys.edx.keys import CourseKey, UsageKey
1214
from opaque_keys.edx.locator import LibraryLocatorV2
1315
from xblocks_contrib.video.exceptions import TranscriptNotFoundError
@@ -32,8 +34,13 @@
3234
clean_video_id,
3335
get_html5_ids,
3436
remove_subs_from_store,
37+
get_transcript_for_video,
38+
get_transcript,
3539
)
3640

41+
from xmodule.exceptions import NotFoundError
42+
43+
3744
log = logging.getLogger(__name__)
3845

3946

@@ -127,17 +134,70 @@ def get_transcript(
127134
TranscriptsGenerationException: If the transcript cannot be found or retrieved
128135
TranscriptNotFoundError: If the transcript cannot be found or retrieved
129136
"""
130-
# Import here to avoid circular dependency
131-
from openedx.core.djangoapps.video_config.transcripts_utils import get_transcript
132-
from xmodule.exceptions import NotFoundError
133-
134137
try:
135138
return get_transcript(video_block, lang, output_format, youtube_id)
136139
except NotFoundError as exc:
137140
raise TranscriptNotFoundError(
138141
f"Failed to get transcript: {exc}"
139142
) from exc
140143

144+
def available_translations(
145+
self,
146+
video_block,
147+
transcripts: dict[str, Any],
148+
verify_assets: bool | None = None,
149+
is_bumper: bool = False,
150+
) -> list[str]:
151+
"""
152+
Return a list of language codes for which we have transcripts.
153+
154+
Arguments:
155+
video_block: The video XBlock instance
156+
transcripts (dict): A dict with all transcripts and a sub.
157+
verify_assets (boolean): If True, checks to ensure that the transcripts
158+
really exist in the contentstore. If False, we just look at the
159+
VideoBlock fields and do not query the contentstore. One reason
160+
we might do this is to avoid slamming contentstore() with queries
161+
when trying to make a listing of videos and their languages.
162+
163+
Defaults to `not FALLBACK_TO_ENGLISH_TRANSCRIPTS`.
164+
165+
is_bumper (boolean): If True, indicates this is a bumper video.
166+
167+
Returns:
168+
list[str]: List of language codes for available transcripts.
169+
"""
170+
translations = []
171+
if verify_assets is None:
172+
verify_assets = not settings.FEATURES.get('FALLBACK_TO_ENGLISH_TRANSCRIPTS')
173+
174+
sub, other_langs = transcripts["sub"], transcripts["transcripts"]
175+
176+
if verify_assets:
177+
all_langs = dict(**other_langs)
178+
if sub:
179+
all_langs.update({'en': sub})
180+
181+
for language, filename in all_langs.items():
182+
try:
183+
# for bumper videos, transcripts are stored in content store only
184+
if is_bumper:
185+
get_transcript_for_video(video_block.location, filename, filename, language)
186+
else:
187+
get_transcript(video_block, language)
188+
except NotFoundError:
189+
continue
190+
191+
translations.append(language)
192+
else:
193+
# If we're not verifying the assets, we just trust our field values
194+
translations = list(other_langs)
195+
if not translations or sub:
196+
translations += ['en']
197+
198+
# to clean redundant language codes.
199+
return list(set(translations))
200+
141201
def upload_transcript(
142202
self,
143203
*,

openedx/core/djangoapps/video_config/transcripts_utils.py

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -749,53 +749,6 @@ class VideoTranscriptsMixin:
749749
This is necessary for VideoBlock.
750750
"""
751751

752-
def available_translations(self, transcripts, verify_assets=None, is_bumper=False):
753-
"""
754-
Return a list of language codes for which we have transcripts.
755-
756-
Arguments:
757-
verify_assets (boolean): If True, checks to ensure that the transcripts
758-
really exist in the contentstore. If False, we just look at the
759-
VideoBlock fields and do not query the contentstore. One reason
760-
we might do this is to avoid slamming contentstore() with queries
761-
when trying to make a listing of videos and their languages.
762-
763-
Defaults to `not FALLBACK_TO_ENGLISH_TRANSCRIPTS`.
764-
765-
transcripts (dict): A dict with all transcripts and a sub.
766-
include_val_transcripts(boolean): If True, adds the edx-val transcript languages as well.
767-
"""
768-
translations = []
769-
if verify_assets is None:
770-
verify_assets = not settings.FEATURES.get('FALLBACK_TO_ENGLISH_TRANSCRIPTS')
771-
772-
sub, other_langs = transcripts["sub"], transcripts["transcripts"]
773-
774-
if verify_assets:
775-
all_langs = dict(**other_langs)
776-
if sub:
777-
all_langs.update({'en': sub})
778-
779-
for language, filename in all_langs.items():
780-
try:
781-
# for bumper videos, transcripts are stored in content store only
782-
if is_bumper:
783-
get_transcript_for_video(self.location, filename, filename, language)
784-
else:
785-
get_transcript(self, language)
786-
except NotFoundError:
787-
continue
788-
789-
translations.append(language)
790-
else:
791-
# If we're not verifying the assets, we just trust our field values
792-
translations = list(other_langs)
793-
if not translations or sub:
794-
translations += ['en']
795-
796-
# to clean redundant language codes.
797-
return list(set(translations))
798-
799752
def get_default_transcript_language(self, transcripts, dest_lang=None):
800753
"""
801754
Returns the default transcript language for this video block.

xmodule/tests/test_video.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,7 +1102,8 @@ def test_video_with_multiple_transcripts_translation_retrieval(self):
11021102
'''
11031103

11041104
block = instantiate_block(data=xml_data_transcripts)
1105-
translations = block.available_translations(block.get_transcripts_info())
1105+
video_config_service = block.runtime.service(block, 'video_config')
1106+
translations = video_config_service.available_translations(block, block.get_transcripts_info())
11061107
assert sorted(translations) == sorted(['hr', 'ge'])
11071108

11081109
def test_video_with_no_transcripts_translation_retrieval(self):
@@ -1112,13 +1113,14 @@ def test_video_with_no_transcripts_translation_retrieval(self):
11121113
does not throw an exception.
11131114
"""
11141115
block = instantiate_block(data=None)
1115-
translations_with_fallback = block.available_translations(block.get_transcripts_info())
1116+
video_config_service = block.runtime.service(block, 'video_config')
1117+
translations_with_fallback = video_config_service.available_translations(block, block.get_transcripts_info())
11161118
assert translations_with_fallback == ['en']
11171119

11181120
with patch.dict(settings.FEATURES, FALLBACK_TO_ENGLISH_TRANSCRIPTS=False):
11191121
# Some organizations don't have English transcripts for all videos
11201122
# This feature makes it configurable
1121-
translations_no_fallback = block.available_translations(block.get_transcripts_info())
1123+
translations_no_fallback = video_config_service.available_translations(block, block.get_transcripts_info())
11221124
assert translations_no_fallback == []
11231125

11241126
@override_settings(ALL_LANGUAGES=ALL_LANGUAGES)
@@ -1142,7 +1144,12 @@ def test_video_with_language_do_not_have_transcripts_translation(self):
11421144
</video>
11431145
'''
11441146
block = instantiate_block(data=xml_data_transcripts)
1145-
translations = block.available_translations(block.get_transcripts_info(), verify_assets=False)
1147+
video_config_service = block.runtime.service(block, 'video_config')
1148+
translations = video_config_service.available_translations(
1149+
block,
1150+
block.get_transcripts_info(),
1151+
verify_assets=False
1152+
)
11461153
assert translations != ['ur']
11471154

11481155
def assert_validation_message(self, validation, expected_msg):

xmodule/video_block/video_block.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1204,7 +1204,14 @@ def student_view_data(self, context=None):
12041204
"file_size": 0, # File size is not relevant for external link
12051205
}
12061206

1207-
available_translations = self.available_translations(self.get_transcripts_info())
1207+
video_config_service = self.runtime.service(self, 'video_config')
1208+
if video_config_service:
1209+
available_translations = video_config_service.available_translations(
1210+
self,
1211+
self.get_transcripts_info()
1212+
)
1213+
else:
1214+
available_translations = []
12081215
transcripts = {
12091216
lang: self.runtime.handler_url(self, 'transcript', 'download', query="lang=" + lang, thirdparty=True)
12101217
for lang in available_translations

xmodule/video_block/video_handlers.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,11 @@ def transcript(self, request, dispatch):
322322
mimetype
323323
)
324324
elif dispatch.startswith('available_translations'):
325-
available_translations = self.available_translations(
325+
video_config_service = self.runtime.service(self, 'video_config')
326+
if not video_config_service:
327+
return Response(status=404)
328+
available_translations = video_config_service.available_translations(
329+
self,
326330
transcripts,
327331
verify_assets=True,
328332
is_bumper=is_bumper
@@ -395,7 +399,14 @@ def validate_transcript_upload_data(self, data):
395399

396400
# Get available transcript languages.
397401
transcripts = self.get_transcripts_info()
398-
available_translations = self.available_translations(transcripts, verify_assets=True)
402+
video_config_service = self.runtime.service(self, 'video_config')
403+
if not video_config_service:
404+
return error
405+
available_translations = video_config_service.available_translations(
406+
self,
407+
transcripts,
408+
verify_assets=True
409+
)
399410

400411
if missing:
401412
error = _('The following parameters are required: {missing}.').format(missing=', '.join(missing))

0 commit comments

Comments
 (0)