Browse Source
perf(caching): add redis and conditional caching across apps, fix bookmark filtering
perf(caching): add redis and conditional caching across apps, fix bookmark filtering
- Add Redis caching (2h TTL) to categories, collections, and pinned collections in video, library, article, podcast, and dobodbi_calendar (6h TTL) - Implement conditional_cached_view (30m TTL) for list endpoints (books, podcasts, articles, videos, playlists) that caches public queries and bypasses caching when is_bookmark=true or is_bookmarked=true - Remove bookmark/user count queries from pinned collection list views to avoid user-specific cache pollution - Prevent user bookmark/rate leakage into public catalog cache in BookListView via AnonymousUser context wrapper - Fix video bookmark filtering bug in VideoPlaylistListAPIView and VideoListAPIView to accept both is_bookmark and is_bookmarked, and query both VIDEO_PLAYLIST and VIDEO bookmarks - Add assign_random_videos_to_playlists management commandmaster
12 changed files with 282 additions and 110 deletions
-
28apps/article/urls.py
-
7apps/article/views.py
-
12apps/dobodbi_calendar/urls.py
-
2apps/library/serializers.py
-
31apps/library/urls.py
-
23apps/library/views.py
-
29apps/podcast/urls.py
-
10apps/podcast/views.py
-
126apps/video/management/commands/assign_random_videos_to_playlists.py
-
13apps/video/serializers.py
-
30apps/video/urls.py
-
81apps/video/views.py
@ -0,0 +1,126 @@ |
|||||
|
import random |
||||
|
from django.core.management.base import BaseCommand |
||||
|
from django.db import transaction |
||||
|
from apps.video.models import Video, VideoPlaylist, PlaylistItem |
||||
|
|
||||
|
|
||||
|
class Command(BaseCommand): |
||||
|
help = 'Assign random videos from the database to all video playlists so each has at least 7-8 items' |
||||
|
|
||||
|
def add_arguments(self, parser): |
||||
|
parser.add_argument( |
||||
|
'--min-items', |
||||
|
type=int, |
||||
|
default=7, |
||||
|
help='Minimum number of videos per playlist (default: 7)' |
||||
|
) |
||||
|
parser.add_argument( |
||||
|
'--max-items', |
||||
|
type=int, |
||||
|
default=8, |
||||
|
help='Maximum number of videos per playlist (default: 8)' |
||||
|
) |
||||
|
parser.add_argument( |
||||
|
'--clear-existing', |
||||
|
action='store_true', |
||||
|
help='Clear existing playlist items before assigning new random videos' |
||||
|
) |
||||
|
|
||||
|
def handle(self, *args, **options): |
||||
|
min_items = options['min_items'] |
||||
|
max_items = options['max_items'] |
||||
|
clear_existing = options['clear_existing'] |
||||
|
|
||||
|
if max_items < min_items: |
||||
|
max_items = min_items |
||||
|
|
||||
|
videos = list(Video.objects.filter(status=True)) |
||||
|
if not videos: |
||||
|
videos = list(Video.objects.all()) |
||||
|
|
||||
|
if not videos: |
||||
|
self.stdout.write(self.style.ERROR('No videos found in the database!')) |
||||
|
return |
||||
|
|
||||
|
playlists = list(VideoPlaylist.objects.all()) |
||||
|
if not playlists: |
||||
|
self.stdout.write(self.style.ERROR('No video playlists found in the database!')) |
||||
|
return |
||||
|
|
||||
|
self.stdout.write(self.style.NOTICE( |
||||
|
f'Found {len(videos)} videos and {len(playlists)} playlists.' |
||||
|
)) |
||||
|
self.stdout.write(f'Target items per playlist: {min_items} to {max_items}\n') |
||||
|
|
||||
|
total_created = 0 |
||||
|
|
||||
|
with transaction.atomic(): |
||||
|
for playlist in playlists: |
||||
|
if clear_existing: |
||||
|
playlist.playlist_items.all().delete() |
||||
|
existing_video_ids = set() |
||||
|
current_count = 0 |
||||
|
else: |
||||
|
existing_items = list(playlist.playlist_items.all()) |
||||
|
existing_video_ids = {item.video_id for item in existing_items} |
||||
|
current_count = len(existing_video_ids) |
||||
|
|
||||
|
target_count = random.randint(min_items, max_items) |
||||
|
|
||||
|
# If the playlist already has at least min_items and clear_existing is False, skip or top up |
||||
|
needed_count = target_count - current_count |
||||
|
if needed_count <= 0: |
||||
|
self.stdout.write( |
||||
|
f' [=] Playlist #{playlist.id} already has {current_count} videos (>= {min_items}).' |
||||
|
) |
||||
|
continue |
||||
|
|
||||
|
# Available videos not already in this playlist |
||||
|
available_videos = [v for v in videos if v.id not in existing_video_ids] |
||||
|
|
||||
|
if not available_videos: |
||||
|
self.stdout.write( |
||||
|
self.style.WARNING( |
||||
|
f' [!] Playlist #{playlist.id}: No more unique videos available to add.' |
||||
|
) |
||||
|
) |
||||
|
continue |
||||
|
|
||||
|
# Select random sample of available videos |
||||
|
sample_size = min(needed_count, len(available_videos)) |
||||
|
selected_videos = random.sample(available_videos, sample_size) |
||||
|
|
||||
|
# Determine starting priority |
||||
|
start_priority = current_count + 1 |
||||
|
new_items = [] |
||||
|
for idx, video in enumerate(selected_videos): |
||||
|
new_items.append( |
||||
|
PlaylistItem( |
||||
|
playlist=playlist, |
||||
|
video=video, |
||||
|
priority=start_priority + idx |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
PlaylistItem.objects.bulk_create(new_items) |
||||
|
total_created += len(new_items) |
||||
|
|
||||
|
# Update total duration of the playlist |
||||
|
try: |
||||
|
playlist.total_time = playlist.calculate_total_time() |
||||
|
playlist.save(update_fields=['total_time']) |
||||
|
except Exception as e: |
||||
|
self.stdout.write(self.style.WARNING(f' Warning updating total_time: {e}')) |
||||
|
|
||||
|
new_total = current_count + len(new_items) |
||||
|
self.stdout.write( |
||||
|
self.style.SUCCESS( |
||||
|
f' [+] Playlist #{playlist.id}: Added {len(new_items)} videos (Total: {new_total})' |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
self.stdout.write( |
||||
|
self.style.SUCCESS( |
||||
|
f'\nDone! Successfully assigned {total_created} video items across {len(playlists)} playlists.' |
||||
|
) |
||||
|
) |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue