From 3486ed0e1592bfc4e9a56bb49201daae236e56c7 Mon Sep 17 00:00:00 2001 From: Markos Gogoulos Date: Sat, 18 Apr 2026 14:01:36 +0300 Subject: [PATCH] this --- .pre-commit-config.yaml | 3 +- cms/version.py | 2 +- files/views/media.py | 3 +- files/views/pages.py | 52 +++++++++++---- .../profile-page/ProfilePagesHeader.js | 2 +- .../search-filters/ProfileMediaSharing.jsx | 2 +- .../js/pages/ProfileSharedWithMePage.js | 63 +++++++++++++++++++ static/js/_commons.js | 2 +- static/js/profile-shared-with-me.js | 2 +- templates/cms/edit_chapters.html | 2 +- 10 files changed, 113 insertions(+), 20 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7f305b08..2f9a1952 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,8 +9,7 @@ repos: - id: isort args: ["--profile", "black"] - repo: https://github.com/psf/black - rev: 23.1.0 + rev: 24.10.0 hooks: - id: black language_version: python3 - additional_dependencies: [ 'click==8.0.4' ] diff --git a/cms/version.py b/cms/version.py index 094cdac8..b5d17105 100644 --- a/cms/version.py +++ b/cms/version.py @@ -1 +1 @@ -VERSION = "8.93" +VERSION = "8.97" diff --git a/files/views/media.py b/files/views/media.py index 4b43dcbf..6ee65064 100644 --- a/files/views/media.py +++ b/files/views/media.py @@ -191,6 +191,7 @@ class MediaList(APIView): conditions |= Q(category__in=rbac_categories) media = base_queryset.filter(conditions).exclude(user=request.user).distinct() + include_sharing_info = True elif author_param: user_queryset = User.objects.all() user = get_object_or_404(user_queryset, username=author_param) @@ -268,7 +269,7 @@ class MediaList(APIView): if include_sharing_info: prefetch_related_objects( page, - Prefetch('permissions', queryset=MediaPermission.objects.select_related('user')), + Prefetch('permissions', queryset=MediaPermission.objects.select_related('user').exclude(user=request.user)), Prefetch('category', queryset=Category.objects.filter(is_rbac_category=True).prefetch_related('rbac_groups'), to_attr='rbac_categories_prefetched'), ) diff --git a/files/views/pages.py b/files/views/pages.py index 0583e0a3..e22504a5 100644 --- a/files/views/pages.py +++ b/files/views/pages.py @@ -7,6 +7,7 @@ from django.contrib.auth.decorators import login_required from django.core.mail import EmailMessage from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.shortcuts import render +from django.utils.html import mark_safe, strip_tags from django.views.decorators.csrf import csrf_exempt from cms.version import VERSION @@ -262,20 +263,42 @@ def video_chapters(request, friendly_token): data = request_data.get("chapters") if data is None: return JsonResponse({'success': False, 'error': 'Request must contain "chapters" array'}, status=400) + if not isinstance(data, list): + return JsonResponse({'success': False, 'error': '"chapters" must be an array'}, status=400) + if len(data) > 200: + return JsonResponse({'success': False, 'error': 'Too many chapters (max 200)'}, status=400) chapters = [] - for _, chapter_data in enumerate(data): + for chapter_data in data: + if not isinstance(chapter_data, dict): + continue start_time = chapter_data.get('startTime') end_time = chapter_data.get('endTime') chapter_title = chapter_data.get('chapterTitle') - if start_time and end_time and chapter_title: - chapters.append( - { - 'startTime': start_time, - 'endTime': end_time, - 'chapterTitle': chapter_title, - } - ) + + if not isinstance(start_time, (int, float)): + continue + if not isinstance(end_time, (int, float)): + continue + if not isinstance(chapter_title, str) or not chapter_title.strip(): + continue + + start_time = float(start_time) + end_time = float(end_time) + if start_time < 0 or end_time < 0 or start_time >= end_time: + continue + + chapter_title = strip_tags(chapter_title).strip()[:500] + if not chapter_title: + continue + + chapters.append( + { + 'startTime': start_time, + 'endTime': end_time, + 'chapterTitle': chapter_title, + } + ) except Exception as e: # noqa return JsonResponse({'success': False, 'error': 'Request data must be a list of video chapters with startTime, endTime, chapterTitle'}, status=400) @@ -449,11 +472,18 @@ def edit_chapters(request): if not (is_mediacms_editor(request.user) or request.user.has_contributor_access_to_media(media)): return HttpResponseRedirect("/") - chapters = media.chapter_data + _html_escapes = str.maketrans({'<': r'\u003C', '>': r'\u003E', '&': r'\u0026'}) + chapters_json = mark_safe(json.dumps(media.chapter_data).translate(_html_escapes)) return render( request, "cms/edit_chapters.html", - {"media_object": media, "add_subtitle_url": media.add_subtitle_url, "media_file_path": helpers.url_from_path(media.media_file.path), "media_id": media.friendly_token, "chapters": chapters}, + { + "media_object": media, + "add_subtitle_url": media.add_subtitle_url, + "media_file_path": helpers.url_from_path(media.media_file.path), + "media_id": media.friendly_token, + "chapters": chapters_json, + }, ) diff --git a/frontend/src/static/js/components/profile-page/ProfilePagesHeader.js b/frontend/src/static/js/components/profile-page/ProfilePagesHeader.js index b480f895..1ff23912 100644 --- a/frontend/src/static/js/components/profile-page/ProfilePagesHeader.js +++ b/frontend/src/static/js/components/profile-page/ProfilePagesHeader.js @@ -492,7 +492,7 @@ class NavMenuInlineTabs extends React.PureComponent { ) : null} {this.props.onToggleSharingClick && - ['media', 'shared_by_me'].includes(this.props.type) ? ( + ['media', 'shared_by_me', 'shared_with_me'].includes(this.props.type) ? (
  • {hasUsers ? (
    -
    {translateString('SHARED WITH USERS')}
    +
    {translateString('USERS SHARING')}
    diff --git a/frontend/src/static/js/pages/ProfileSharedWithMePage.js b/frontend/src/static/js/pages/ProfileSharedWithMePage.js index 63eac7d7..d6bc3aa5 100644 --- a/frontend/src/static/js/pages/ProfileSharedWithMePage.js +++ b/frontend/src/static/js/pages/ProfileSharedWithMePage.js @@ -9,6 +9,7 @@ import ProfilePagesContent from '../components/profile-page/ProfilePagesContent' import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync'; import { ProfileMediaFilters } from '../components/search-filters/ProfileMediaFilters'; import { ProfileMediaTags } from '../components/search-filters/ProfileMediaTags'; +import { ProfileMediaSharing } from '../components/search-filters/ProfileMediaSharing'; import { ProfileMediaSorting } from '../components/search-filters/ProfileMediaSorting'; import { inEmbeddedApp, inSelectMediaEmbedMode } from '../utils/helpers'; @@ -45,11 +46,16 @@ export class ProfileSharedWithMePage extends Page { hiddenFilters: true, hiddenTags: true, hiddenSorting: true, + hiddenSharing: true, filterArgs: '', availableTags: [], selectedTag: 'all', selectedSort: 'date_added_desc', selectedMedia: new Set(), // For select media mode + sharedUsers: [], + sharedGroups: [], + selectedSharingType: null, + selectedSharingValue: null, }; this.authorDataLoad = this.authorDataLoad.bind(this); @@ -59,9 +65,11 @@ export class ProfileSharedWithMePage extends Page { this.onToggleFiltersClick = this.onToggleFiltersClick.bind(this); this.onToggleTagsClick = this.onToggleTagsClick.bind(this); this.onToggleSortingClick = this.onToggleSortingClick.bind(this); + this.onToggleSharingClick = this.onToggleSharingClick.bind(this); this.onFiltersUpdate = this.onFiltersUpdate.bind(this); this.onTagSelect = this.onTagSelect.bind(this); this.onSortSelect = this.onSortSelect.bind(this); + this.onSharingSelect = this.onSharingSelect.bind(this); this.onResponseDataLoaded = this.onResponseDataLoaded.bind(this); this.handleMediaSelection = this.handleMediaSelection.bind(this); @@ -175,6 +183,7 @@ export class ProfileSharedWithMePage extends Page { hiddenFilters: !this.state.hiddenFilters, hiddenTags: true, hiddenSorting: true, + hiddenSharing: true, }); } @@ -183,6 +192,7 @@ export class ProfileSharedWithMePage extends Page { hiddenFilters: true, hiddenTags: !this.state.hiddenTags, hiddenSorting: true, + hiddenSharing: true, }); } @@ -191,6 +201,16 @@ export class ProfileSharedWithMePage extends Page { hiddenFilters: true, hiddenTags: true, hiddenSorting: !this.state.hiddenSorting, + hiddenSharing: true, + }); + } + + onToggleSharingClick() { + this.setState({ + hiddenFilters: true, + hiddenTags: true, + hiddenSorting: true, + hiddenSharing: !this.state.hiddenSharing, }); } @@ -203,6 +223,8 @@ export class ProfileSharedWithMePage extends Page { publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1], sort_by: this.state.selectedSort, tag: tag, + sharing_type: this.state.selectedSharingType, + sharing_value: this.state.selectedSharingValue, }); }); } @@ -216,6 +238,23 @@ export class ProfileSharedWithMePage extends Page { publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1], sort_by: sortBy, tag: this.state.selectedTag, + sharing_type: this.state.selectedSharingType, + sharing_value: this.state.selectedSharingValue, + }); + }); + } + + onSharingSelect(type, value) { + this.setState({ selectedSharingType: type, selectedSharingValue: value }, () => { + this.onFiltersUpdate({ + media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1], + upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1], + duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1], + publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1], + sort_by: this.state.selectedSort, + tag: this.state.selectedTag, + sharing_type: type, + sharing_value: value, }); }); } @@ -229,6 +268,8 @@ export class ProfileSharedWithMePage extends Page { sort_by: null, ordering: null, t: null, + shared_user: null, + shared_group: null, }; switch (updatedArgs.media_type) { @@ -290,6 +331,12 @@ export class ProfileSharedWithMePage extends Page { args.t = updatedArgs.tag; } + if (updatedArgs.sharing_type === 'user' && updatedArgs.sharing_value) { + args.shared_user = updatedArgs.sharing_value; + } else if (updatedArgs.sharing_type === 'group' && updatedArgs.sharing_value) { + args.shared_group = updatedArgs.sharing_value; + } + const newArgs = []; for (let arg in args) { @@ -341,6 +388,12 @@ export class ProfileSharedWithMePage extends Page { .filter((tag) => tag); this.setState({ availableTags: tags }); } + if (responseData && responseData.shared_users !== undefined) { + this.setState({ + sharedUsers: responseData.shared_users || [], + sharedGroups: responseData.shared_groups || [], + }); + } } handleMediaSelection(mediaId, isSelected) { @@ -411,9 +464,11 @@ export class ProfileSharedWithMePage extends Page { onToggleFiltersClick={this.onToggleFiltersClick} onToggleTagsClick={this.onToggleTagsClick} onToggleSortingClick={this.onToggleSortingClick} + onToggleSharingClick={this.onToggleSharingClick} hasActiveFilters={hasActiveFilters} hasActiveTags={this.state.selectedTag !== 'all'} hasActiveSort={this.state.selectedSort !== 'date_added_desc'} + hasActiveSharing={!!this.state.selectedSharingValue} hideChannelBanner={inEmbeddedApp()} /> ) : null, @@ -435,6 +490,14 @@ export class ProfileSharedWithMePage extends Page { onTagSelect={this.onTagSelect} />