This commit is contained in:
Markos Gogoulos
2026-04-20 09:46:39 +03:00
parent 1261719996
commit 9539741d11
8 changed files with 66 additions and 19 deletions
+4
View File
@@ -79,6 +79,10 @@ urlpatterns = [
rf"^api/v1/media/{friendly_token}/trim_video$",
views.trim_video,
),
re_path(
rf"^api/v1/media/{friendly_token}/share$",
views.MediaShare.as_view(),
),
re_path(r"^api/v1/categories$", views.CategoryList.as_view()),
re_path(r"^api/v1/categories/contributor$", views.CategoryListContributor.as_view()),
re_path(r"^api/v1/tags$", views.TagList.as_view()),
+21
View File
@@ -1212,3 +1212,24 @@ class MediaSearch(APIView):
page = paginator.paginate_queryset(media, request)
serializer = MediaSearchSerializer(page, many=True, context={"request": request})
return paginator.get_paginated_response(serializer.data)
class MediaShare(APIView):
"""Create a self-referential MediaPermission to mark a media as shared."""
def post(self, request, friendly_token):
if not request.user.is_authenticated:
return Response({'error': 'Authentication required'}, status=status.HTTP_401_UNAUTHORIZED)
media = get_object_or_404(Media, friendly_token=friendly_token)
if media.user != request.user and not is_mediacms_editor(request.user):
return Response({'error': 'Permission denied'}, status=status.HTTP_403_FORBIDDEN)
MediaPermission.objects.get_or_create(
media=media,
user=request.user,
defaults={'owner_user': request.user, 'permission': 'owner'},
)
return Response({'status': 'ok'})
@@ -159,26 +159,35 @@ class ProfileMediaPage extends Page {
handleMediaSelection(mediaId, isSelected) {
// Only used in select media embed mode; normal mode is handled by bulkActions
this.setState((prevState) => {
this.setState(() => {
const newSelectedMedia = new Set();
if (isSelected) {
newSelectedMedia.add(mediaId);
if (window.parent !== window) {
const baseUrl = window.location.origin;
const embedUrl = `${baseUrl}/embed?m=${mediaId}`;
window.parent.postMessage({
type: 'videoSelected',
embedUrl: embedUrl,
videoId: mediaId
}, '*');
}
}
return { selectedMedia: newSelectedMedia };
});
if (isSelected) {
if (window.parent !== window) {
const baseUrl = window.location.origin;
const embedUrl = `${baseUrl}/embed?m=${mediaId}`;
window.parent.postMessage({
type: 'videoSelected',
embedUrl: embedUrl,
videoId: mediaId,
}, '*');
}
// Mark media as shared so LTI users in the course can access it
fetch(`/api/v1/media/${mediaId}/share`, {
method: 'POST',
headers: {
'X-CSRFToken': this.props.bulkActions.getCsrfToken(),
'Content-Type': 'application/json',
},
}).catch(() => {});
}
}
onToggleFiltersClick() {
+14 -1
View File
@@ -700,7 +700,8 @@ class EmbedMediaLTIView(View):
if lti_session and request.user.is_authenticated:
context_id = lti_session.get('context_id')
platform_id = lti_session.get('platform_id')
if context_id and platform_id:
if media.is_shared and context_id and platform_id:
try:
resource_link = (
LTIResourceLink.objects.filter(
@@ -728,6 +729,18 @@ class EmbedMediaLTIView(View):
except Exception:
logger.exception('EmbedMediaLTIView: error checking course access for user=%s media=%s', request.user, friendly_token)
if not can_view and media.state == 'private':
has_rbac_access = media.category.filter(
is_rbac_category=True,
rbac_groups__members=request.user,
).exists()
has_direct_permission = MediaPermission.objects.filter(
media=media,
user=request.user,
).exists()
if has_rbac_access or has_direct_permission:
can_view = True
if not can_view and media.state in ["public", "unlisted"]:
can_view = True
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long