2 Commits
e2e7463dac
...
59ad3cd064
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
59ad3cd064 |
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 command |
1 month ago |
|
|
d1094a4725 |
perf(hadis): optimize sync api, eliminate N+1 queries, add redis caching and gzip
- Eliminate N+1 queries in hadis serializers by using in-memory prefetch cache instead of .first() and .order_by() - Add select_related for hadis and category in HadisSyncView prefetches - Apply cached_view (Redis 2h + Accept-Language) to HadisSyncView and public hadis endpoints - Enable GZipMiddleware in Django settings to compress large API responses |
1 month ago |
16 changed files with 344 additions and 164 deletions
-
28apps/article/urls.py
-
7apps/article/views.py
-
12apps/dobodbi_calendar/urls.py
-
25apps/hadis/serializers/hadis.py
-
86apps/hadis/urls.py
-
4apps/hadis/views/hadis.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
-
1config/settings/base.py
@ -185,7 +185,7 @@ class BookSerializer(serializers.ModelSerializer): |
|||||
# user = User.objects.get(email='[email protected]') |
# user = User.objects.get(email='[email protected]') |
||||
# # Get the current user from the request context |
# # Get the current user from the request context |
||||
request = self.context.get('request') |
request = self.context.get('request') |
||||
user = request.user if request and request.user.is_authenticated else None |
|
||||
|
user = request.user if request and getattr(request.user, 'is_authenticated', False) else None |
||||
|
|
||||
if not user: |
if not user: |
||||
return { |
return { |
||||
|
|||||
@ -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