Skip to content

Commit

Permalink
feat: update video and asset api to include usage loactions (#33746)
Browse files Browse the repository at this point in the history
  • Loading branch information
KristinAoki authored Nov 22, 2023
1 parent 119bb46 commit ce2734e
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 43 deletions.
78 changes: 51 additions & 27 deletions cms/djangoapps/contentstore/asset_storage_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,24 @@ def handle_assets(request, course_key_string=None, asset_key_string=None):
return HttpResponseNotFound()


def get_asset_usage_path(request, course_key, asset_key_string):
def get_asset_usage_path_json(request, course_key, asset_key_string):
"""
Get a list of units with ancestors that use given asset.
"""
course_key = CourseKey.from_string(course_key)
if not has_course_author_access(request.user, course_key):
raise PermissionDenied()
asset_location = AssetKey.from_string(asset_key_string) if asset_key_string else None
usage_locations = _get_asset_usage_path(course_key, [{'asset_key': asset_location}])
return JsonResponse({'usage_locations': usage_locations})


def _get_asset_usage_path(course_key, assets):
"""
Get a list of units with ancestors that use given asset.
"""
store = modulestore()
usage_locations = []
static_path = StaticContent.get_static_path_from_location(asset_location)
usage_locations = {str(asset['asset_key']): [] for asset in assets}
verticals = store.get_items(
course_key,
qualifiers={
Expand All @@ -114,26 +121,34 @@ def get_asset_usage_path(request, course_key, asset_key_string):
blocks.extend(vertical.get_children())

for block in blocks:
is_video_block = getattr(block, 'category', '') == 'video'
if is_video_block:
handout = getattr(block, 'handout', '')
if handout and str(asset_location) in handout:
unit = block.get_parent()
subsection = unit.get_parent()
subsection_display_name = getattr(subsection, 'display_name', '')
unit_display_name = getattr(unit, 'display_name', '')
xblock_display_name = getattr(block, 'display_name', '')
usage_locations.append(f'{subsection_display_name} - {unit_display_name} / {xblock_display_name}')
else:
data = getattr(block, 'data', '')
if static_path in data or str(asset_location) in data:
unit = block.get_parent()
subsection = unit.get_parent()
subsection_display_name = getattr(subsection, 'display_name', '')
unit_display_name = getattr(unit, 'display_name', '')
xblock_display_name = getattr(block, 'display_name', '')
usage_locations.append(f'{subsection_display_name} - {unit_display_name} / {xblock_display_name}')
return JsonResponse({'usage_locations': usage_locations})
for asset in assets:
asset_key = asset['asset_key']
asset_key_string = str(asset_key)
static_path = StaticContent.get_static_path_from_location(asset_key)
is_video_block = getattr(block, 'category', '') == 'video'
if is_video_block:
handout = getattr(block, 'handout', '')
if handout and asset_key_string in handout:
unit = block.get_parent()
subsection = unit.get_parent()
subsection_display_name = getattr(subsection, 'display_name', '')
unit_display_name = getattr(unit, 'display_name', '')
xblock_display_name = getattr(block, 'display_name', '')
current_locations = usage_locations[asset_key_string]
new_location = f'{subsection_display_name} - {unit_display_name} / {xblock_display_name}'
usage_locations[asset_key_string] = [*current_locations, new_location]
else:
data = getattr(block, 'data', '')
if static_path in data or asset_key_string in data:
unit = block.get_parent()
subsection = unit.get_parent()
subsection_display_name = getattr(subsection, 'display_name', '')
unit_display_name = getattr(unit, 'display_name', '')
xblock_display_name = getattr(block, 'display_name', '')
current_locations = usage_locations[asset_key_string]
new_location = f'{subsection_display_name} - {unit_display_name} / {xblock_display_name}'
usage_locations[asset_key_string] = [*current_locations, new_location]
return usage_locations


def _asset_index(request, course_key):
Expand Down Expand Up @@ -196,14 +211,16 @@ def _assets_json(request, course_key):

assets, total_count = _get_assets_for_page(course_key, query_options)

assets_usage_locations_map = _get_asset_usage_path(course_key, assets)

if request_options['requested_page'] > 0 and first_asset_to_display_index >= total_count and total_count > 0: # lint-amnesty, pylint: disable=chained-comparison
_update_options_to_requery_final_page(query_options, total_count)
current_page = query_options['current_page']
first_asset_to_display_index = _get_first_asset_index(current_page, requested_page_size)
assets, total_count = _get_assets_for_page(course_key, query_options)

last_asset_to_display_index = first_asset_to_display_index + len(assets)
assets_in_json_format = _get_assets_in_json_format(assets, course_key)
assets_in_json_format = _get_assets_in_json_format(assets, course_key, assets_usage_locations_map)

response_payload = {
'start': first_asset_to_display_index,
Expand Down Expand Up @@ -412,10 +429,13 @@ def _update_options_to_requery_final_page(query_options, total_asset_count):
query_options['current_page'] = int(math.floor((total_asset_count - 1) / query_options['page_size']))


def _get_assets_in_json_format(assets, course_key):
def _get_assets_in_json_format(assets, course_key, assets_usage_locations_map):
"""returns assets information in JSON Format"""
assets_in_json_format = []
for asset in assets:
asset_key = asset['asset_key']
asset_key_string = str(asset_key)
usage_locations = getattr(assets_usage_locations_map, 'asset_key_string', [])
thumbnail_asset_key = _get_thumbnail_asset_key(asset, course_key)
asset_is_locked = asset.get('locked', False)
asset_file_size = asset.get('length', None)
Expand All @@ -424,11 +444,12 @@ def _get_assets_in_json_format(assets, course_key):
asset['displayname'],
asset['contentType'],
asset['uploadDate'],
asset['asset_key'],
asset_key,
thumbnail_asset_key,
asset_is_locked,
course_key,
asset_file_size,
usage_locations,
)

assets_in_json_format.append(asset_in_json)
Expand Down Expand Up @@ -670,13 +691,15 @@ def _delete_thumbnail(thumbnail_location, course_key, asset_key): # lint-amnest
logging.warning('Could not delete thumbnail: %s', thumbnail_location)


def get_asset_json(display_name, content_type, date, location, thumbnail_location, locked, course_key, file_size=None):
def get_asset_json(display_name, content_type, date, location, thumbnail_location,
locked, course_key, file_size=None, usage=None):
'''
Helper method for formatting the asset information to send to client.
'''
asset_url = StaticContent.serialize_asset_key_with_slash(location)
external_url = urljoin(configuration_helpers.get_value('LMS_ROOT_URL', settings.LMS_ROOT_URL), asset_url)
portable_url = StaticContent.get_static_path_from_location(location)
usage_locations = [] if usage is None else usage
return {
'display_name': display_name,
'content_type': content_type,
Expand All @@ -690,4 +713,5 @@ def get_asset_json(display_name, content_type, date, location, thumbnail_locatio
# needed for Backbone delete/update.
'id': str(location),
'file_size': file_size,
'usage_locations': usage_locations,
}
3 changes: 3 additions & 0 deletions cms/djangoapps/contentstore/rest_api/v1/serializers/videos.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ class VideoModelSerializer(serializers.Serializer):
transcripts = serializers.ListField(
child=serializers.CharField()
)
usage_locations = serializers.ListField(
child=serializers.CharField()
)


class VideoActiveTranscriptPreferencesSerializer(serializers.Serializer):
Expand Down
2 changes: 1 addition & 1 deletion cms/djangoapps/contentstore/rest_api/v1/views/videos.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,6 @@ def get(self, request: Request, course_id: str, edx_video_id: str):
if not has_studio_read_access(request.user, course_key):
self.permission_denied(request)

usage_locations = get_video_usage_path(request, course_key, edx_video_id)
usage_locations = get_video_usage_path(course_key, edx_video_id)
serializer = VideoUsageSerializer(usage_locations)
return Response(serializer.data)
15 changes: 6 additions & 9 deletions cms/djangoapps/contentstore/video_storage_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from boto import s3
from django.conf import settings
from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.exceptions import PermissionDenied
from django.http import FileResponse, HttpResponseNotFound
from django.shortcuts import redirect
from django.utils.translation import gettext as _
Expand All @@ -42,7 +41,6 @@
from rest_framework.response import Response

from common.djangoapps.edxmako.shortcuts import render_to_response
from common.djangoapps.student.auth import has_course_author_access
from common.djangoapps.util.json_request import JsonResponse
from openedx.core.djangoapps.video_config.models import VideoTranscriptEnabledFlag
from openedx.core.djangoapps.video_config.toggles import PUBLIC_VIDEO_SHARE
Expand Down Expand Up @@ -223,13 +221,11 @@ def handle_videos(request, course_key_string, edx_video_id=None):
return JsonResponse(data, status=status)


def get_video_usage_path(request, course_key, edx_video_id):
def get_video_usage_path(course_key, edx_video_id):
"""
API for fetching the locations a specific video is used in a course.
Returns a list of paths to a video.
"""
if not has_course_author_access(request.user, course_key):
raise PermissionDenied()
store = modulestore()
usage_locations = []
videos = store.get_items(
Expand Down Expand Up @@ -616,15 +612,16 @@ def _get_index_videos(course, pagination_conf=None):
'transcript_urls', 'error_description'
]

def _get_values(video):
def _get_values(video, course):
"""
Get data for predefined video attributes.
"""
values = {}
values["usage_locations"] = get_video_usage_path(course.id, video["edx_video_id"])['usage_locations']
for attr in attrs:
if attr == 'courses':
course = [c for c in video['courses'] if course_id in c]
(__, values['course_video_image_url']), = list(course[0].items())
current_course = [c for c in video['courses'] if course_id in c]
(__, values['course_video_image_url']), = list(current_course[0].items())
elif attr == 'encoded_videos':
values['download_link'] = ''
values['file_size'] = 0
Expand All @@ -637,7 +634,7 @@ def _get_values(video):
return values

videos, pagination_context = _get_videos(course, pagination_conf)
return [_get_values(video) for video in videos], pagination_context
return [_get_values(video, course) for video in videos], pagination_context


def get_all_transcript_languages():
Expand Down
4 changes: 2 additions & 2 deletions cms/djangoapps/contentstore/views/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.views.decorators.csrf import ensure_csrf_cookie
from cms.djangoapps.contentstore.asset_storage_handlers import (
handle_assets,
get_asset_usage_path,
get_asset_usage_path_json,
update_course_run_asset as update_course_run_asset_source_function,
get_file_size as get_file_size_source_function,
delete_asset as delete_asset_source_function,
Expand Down Expand Up @@ -57,7 +57,7 @@ def assets_handler(request, course_key_string=None, asset_key_string=None):
@login_required
@ensure_csrf_cookie
def asset_usage_path_handler(request, course_key_string, asset_key_string):
return get_asset_usage_path(request, course_key_string, asset_key_string)
return get_asset_usage_path_json(request, course_key_string, asset_key_string)


def update_course_run_asset(course_key, upload_file):
Expand Down
4 changes: 3 additions & 1 deletion cms/djangoapps/contentstore/views/tests/test_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def upload_asset(self, name="asset-1", asset_type='text'):
Post to the asset upload url
"""
asset = self.get_sample_asset(name, asset_type)
print(asset)
response = self.client.post(self.url, {"name": name, "file": asset})
return response

Expand Down Expand Up @@ -242,7 +243,8 @@ def test_mocked_filtered_response(self, mock_get_all_content_for_course):
"thumbnail": None,
"thumbnail_location": thumbnail_location,
"locked": None,
"static_full_url": "/assets/courseware/v1/asset-v1:org+class+run+type@asset+block@my_file_name.jpg"
"static_full_url": "/assets/courseware/v1/asset-v1:org+class+run+type@asset+block@my_file_name.jpg",
"usage_locations": {str(asset_key): []}
}
],
1
Expand Down
9 changes: 6 additions & 3 deletions cms/djangoapps/contentstore/views/tests/test_videos.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,8 @@ def test_get_json(self):
'transcripts',
'transcription_status',
'transcript_urls',
'error_description'
'error_description',
'usage_locations'
}
)
dateutil.parser.parse(response_video['created'])
Expand All @@ -389,7 +390,8 @@ def test_get_json(self):
[
'edx_video_id', 'client_video_id', 'created', 'duration',
'status', 'course_video_image_url', 'file_size', 'download_link',
'transcripts', 'transcription_status', 'transcript_urls', 'error_description'
'transcripts', 'transcription_status', 'transcript_urls',
'error_description', 'usage_locations'
],
[
{
Expand All @@ -406,7 +408,8 @@ def test_get_json(self):
[
'edx_video_id', 'client_video_id', 'created', 'duration',
'status', 'course_video_image_url', 'file_size', 'download_link',
'transcripts', 'transcription_status', 'transcript_urls', 'error_description'
'transcripts', 'transcription_status', 'transcript_urls',
'error_description', 'usage_locations'
],
[
{
Expand Down

0 comments on commit ce2734e

Please sign in to comment.