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