diff --git a/apps/epg/tasks.py b/apps/epg/tasks.py index dc0fdbe6..45b17bb7 100644 --- a/apps/epg/tasks.py +++ b/apps/epg/tasks.py @@ -879,6 +879,8 @@ def parse_channels_only(source): display_name = child.text.strip() elif child.tag == 'icon': icon_url = child.get('src', '').strip() + if display_name and icon_url: + break # No need to continue if we have both if not display_name: display_name = tvg_id diff --git a/apps/output/views.py b/apps/output/views.py index 6c18629c..bbf79acf 100644 --- a/apps/output/views.py +++ b/apps/output/views.py @@ -1008,31 +1008,11 @@ def xc_get_vod_categories(user): response = [] - # Filter categories based on user's M3U accounts - if user.user_level == 0: - # For regular users, get categories from their accessible M3U accounts - if user.channel_profiles.count() > 0: - channel_profiles = user.channel_profiles.all() - # Get M3U accounts accessible through user's profiles - from apps.m3u.models import M3UAccount - m3u_accounts = M3UAccount.objects.filter( - is_active=True, - profiles__in=channel_profiles - ).distinct() - else: - m3u_accounts = [] - - # Get categories that have movie relations with these accounts - categories = VODCategory.objects.filter( - category_type='movie', - m3umovierelation__m3u_account__in=m3u_accounts - ).distinct().order_by(Lower("name")) - else: - # Admins can see all categories that have active movie relations - categories = VODCategory.objects.filter( - category_type='movie', - m3umovierelation__m3u_account__is_active=True - ).distinct().order_by(Lower("name")) + # All authenticated users get access to VOD from all active M3U accounts + categories = VODCategory.objects.filter( + category_type='movie', + m3umovierelation__m3u_account__is_active=True + ).distinct().order_by(Lower("name")) for category in categories: response.append({ @@ -1051,22 +1031,9 @@ def xc_get_vod_streams(request, user, category_id=None): streams = [] - # Build filters for movies based on user access + # All authenticated users get access to VOD from all active M3U accounts filters = {"m3u_relations__m3u_account__is_active": True} - if user.user_level == 0: - # For regular users, filter by accessible M3U accounts - if user.channel_profiles.count() > 0: - channel_profiles = user.channel_profiles.all() - from apps.m3u.models import M3UAccount - m3u_accounts = M3UAccount.objects.filter( - is_active=True, - profiles__in=channel_profiles - ).distinct() - filters["m3u_relations__m3u_account__in"] = m3u_accounts - else: - return [] # No accessible accounts - if category_id: filters["m3u_relations__category_id"] = category_id @@ -1127,28 +1094,11 @@ def xc_get_series_categories(user): response = [] - # Similar filtering as VOD categories but for series - if user.user_level == 0: - if user.channel_profiles.count() > 0: - channel_profiles = user.channel_profiles.all() - from apps.m3u.models import M3UAccount - m3u_accounts = M3UAccount.objects.filter( - is_active=True, - profiles__in=channel_profiles - ).distinct() - else: - m3u_accounts = [] - - # Get categories that have series relations with these accounts - categories = VODCategory.objects.filter( - category_type='series', - m3useriesrelation__m3u_account__in=m3u_accounts - ).distinct().order_by(Lower("name")) - else: - categories = VODCategory.objects.filter( - category_type='series', - m3useriesrelation__m3u_account__is_active=True - ).distinct().order_by(Lower("name")) + # All authenticated users get access to series from all active M3U accounts + categories = VODCategory.objects.filter( + category_type='series', + m3useriesrelation__m3u_account__is_active=True + ).distinct().order_by(Lower("name")) for category in categories: response.append({ @@ -1166,21 +1116,9 @@ def xc_get_series(request, user, category_id=None): series_list = [] - # Build filters based on user access + # All authenticated users get access to series from all active M3U accounts filters = {"m3u_account__is_active": True} - if user.user_level == 0: - if user.channel_profiles.count() > 0: - channel_profiles = user.channel_profiles.all() - from apps.m3u.models import M3UAccount - m3u_accounts = M3UAccount.objects.filter( - is_active=True, - profiles__in=channel_profiles - ).distinct() - filters["m3u_account__in"] = m3u_accounts - else: - return [] - if category_id: filters["category_id"] = category_id @@ -1228,21 +1166,9 @@ def xc_get_series_info(request, user, series_id): if not series_id: raise Http404() - # Get series relation with user access filtering + # All authenticated users get access to series from all active M3U accounts filters = {"id": series_id, "m3u_account__is_active": True} - if user.user_level == 0: - if user.channel_profiles.count() > 0: - channel_profiles = user.channel_profiles.all() - from apps.m3u.models import M3UAccount - m3u_accounts = M3UAccount.objects.filter( - is_active=True, - profiles__in=channel_profiles - ).distinct() - filters["m3u_account__in"] = m3u_accounts - else: - raise Http404() - try: series_relation = M3USeriesRelation.objects.select_related('series', 'series__logo').get(**filters) series = series_relation.series @@ -1439,21 +1365,9 @@ def xc_get_vod_info(request, user, vod_id): if not vod_id: raise Http404() - # Get movie relation with user access filtering - use movie ID instead of relation ID + # All authenticated users get access to VOD from all active M3U accounts filters = {"movie_id": vod_id, "m3u_account__is_active": True} - if user.user_level == 0: - if user.channel_profiles.count() > 0: - channel_profiles = user.channel_profiles.all() - from apps.m3u.models import M3UAccount - m3u_accounts = M3UAccount.objects.filter( - is_active=True, - profiles__in=channel_profiles - ).distinct() - filters["m3u_account__in"] = m3u_accounts - else: - raise Http404() - try: # Order by account priority to get the best relation when multiple exist movie_relation = M3UMovieRelation.objects.select_related('movie', 'movie__logo').filter(**filters).order_by('-m3u_account__priority', 'id').first() @@ -1602,22 +1516,9 @@ def xc_movie_stream(request, username, password, stream_id, extension): if custom_properties["xc_password"] != password: return JsonResponse({"error": "Invalid credentials"}, status=401) - # Get movie relation based on user access level - use movie ID instead of relation ID + # All authenticated users get access to VOD from all active M3U accounts filters = {"movie_id": stream_id, "m3u_account__is_active": True} - if user.user_level < 10: - # For regular users, filter by accessible M3U accounts - if user.channel_profiles.count() > 0: - channel_profiles = user.channel_profiles.all() - from apps.m3u.models import M3UAccount - m3u_accounts = M3UAccount.objects.filter( - is_active=True, - profiles__in=channel_profiles - ).distinct() - filters["m3u_account__in"] = m3u_accounts - else: - return JsonResponse({"error": "No accessible content"}, status=403) - try: # Order by account priority to get the best relation when multiple exist movie_relation = M3UMovieRelation.objects.select_related('movie').filter(**filters).order_by('-m3u_account__priority', 'id').first() @@ -1652,22 +1553,9 @@ def xc_series_stream(request, username, password, stream_id, extension): if custom_properties["xc_password"] != password: return JsonResponse({"error": "Invalid credentials"}, status=401) - # Get episode relation based on user access level - use episode ID instead of stream_id + # All authenticated users get access to series/episodes from all active M3U accounts filters = {"episode_id": stream_id, "m3u_account__is_active": True} - if user.user_level < 10: - # For regular users, filter by accessible M3U accounts - if user.channel_profiles.count() > 0: - channel_profiles = user.channel_profiles.all() - from apps.m3u.models import M3UAccount - m3u_accounts = M3UAccount.objects.filter( - is_active=True, - profiles__in=channel_profiles - ).distinct() - filters["m3u_account__in"] = m3u_accounts - else: - return JsonResponse({"error": "No accessible content"}, status=403) - try: episode_relation = M3UEpisodeRelation.objects.select_related('episode').get(**filters) except M3UEpisodeRelation.DoesNotExist: diff --git a/apps/vod/tasks.py b/apps/vod/tasks.py index 3c429e15..504b09fc 100644 --- a/apps/vod/tasks.py +++ b/apps/vod/tasks.py @@ -62,9 +62,9 @@ def refresh_vod_content(account_id): logger.info(f"Batch VOD refresh completed for account {account.name} in {duration:.2f} seconds") - # Cleanup orphaned VOD content after refresh - logger.info("Starting cleanup of orphaned VOD content") - cleanup_result = cleanup_orphaned_vod_content(scan_start_time=start_time) + # Cleanup orphaned VOD content after refresh (scoped to this account only) + logger.info(f"Starting cleanup of orphaned VOD content for account {account.name}") + cleanup_result = cleanup_orphaned_vod_content(account_id=account_id, scan_start_time=start_time) logger.info(f"VOD cleanup completed: {cleanup_result}") # Send completion notification @@ -1308,7 +1308,7 @@ def batch_refresh_series_episodes(account_id, series_ids=None): @shared_task -def cleanup_orphaned_vod_content(stale_days=0, scan_start_time=None): +def cleanup_orphaned_vod_content(stale_days=0, scan_start_time=None, account_id=None): """Clean up VOD content that has no M3U relations or has stale relations""" from datetime import timedelta @@ -1318,30 +1318,44 @@ def cleanup_orphaned_vod_content(stale_days=0, scan_start_time=None): # Calculate cutoff date for stale relations cutoff_date = reference_time - timedelta(days=stale_days) + # Build base query filters + base_filters = {'last_seen__lt': cutoff_date} + if account_id: + base_filters['m3u_account_id'] = account_id + logger.info(f"Cleaning up stale VOD content for account {account_id}") + else: + logger.info("Cleaning up stale VOD content across all accounts") + # Clean up stale movie relations (haven't been seen in the specified days) - stale_movie_relations = M3UMovieRelation.objects.filter(last_seen__lt=cutoff_date) + stale_movie_relations = M3UMovieRelation.objects.filter(**base_filters) stale_movie_count = stale_movie_relations.count() stale_movie_relations.delete() # Clean up stale series relations - stale_series_relations = M3USeriesRelation.objects.filter(last_seen__lt=cutoff_date) + stale_series_relations = M3USeriesRelation.objects.filter(**base_filters) stale_series_count = stale_series_relations.count() stale_series_relations.delete() # Clean up stale episode relations - stale_episode_relations = M3UEpisodeRelation.objects.filter(last_seen__lt=cutoff_date) + stale_episode_relations = M3UEpisodeRelation.objects.filter(**base_filters) stale_episode_count = stale_episode_relations.count() stale_episode_relations.delete() - # Clean up movies with no relations (orphaned) - orphaned_movies = Movie.objects.filter(m3u_relations__isnull=True) - orphaned_movie_count = orphaned_movies.count() - orphaned_movies.delete() + # Clean up movies with no relations (orphaned) - only if no account_id specified (global cleanup) + if not account_id: + orphaned_movies = Movie.objects.filter(m3u_relations__isnull=True) + orphaned_movie_count = orphaned_movies.count() + orphaned_movies.delete() - # Clean up series with no relations (orphaned) - orphaned_series = Series.objects.filter(m3u_relations__isnull=True) - orphaned_series_count = orphaned_series.count() - orphaned_series.delete() + # Clean up series with no relations (orphaned) - only if no account_id specified (global cleanup) + orphaned_series = Series.objects.filter(m3u_relations__isnull=True) + orphaned_series_count = orphaned_series.count() + orphaned_series.delete() + else: + # When cleaning up for specific account, we don't remove orphaned content + # as other accounts might still reference it + orphaned_movie_count = 0 + orphaned_series_count = 0 # Episodes will be cleaned up via CASCADE when series are deleted