diff --git a/apps/channels/api_views.py b/apps/channels/api_views.py index 8ea5db8a..a87bac90 100644 --- a/apps/channels/api_views.py +++ b/apps/channels/api_views.py @@ -8,6 +8,7 @@ from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi from django.shortcuts import get_object_or_404, get_list_or_404 from django.db import transaction +from django.db.models import Count from django.db.models import Q import os, json, requests, logging from urllib.parse import unquote @@ -148,7 +149,8 @@ class StreamViewSet(viewsets.ModelViewSet): unassigned = self.request.query_params.get("unassigned") if unassigned == "1": - qs = qs.filter(channels__isnull=True) + # Use annotation with Count for better performance on large datasets + qs = qs.annotate(channel_count=Count('channels')).filter(channel_count=0) channel_group = self.request.query_params.get("channel_group") if channel_group: diff --git a/frontend/src/components/tables/StreamsTable.jsx b/frontend/src/components/tables/StreamsTable.jsx index 167d2532..61fe36b7 100644 --- a/frontend/src/components/tables/StreamsTable.jsx +++ b/frontend/src/components/tables/StreamsTable.jsx @@ -397,70 +397,73 @@ const StreamsTable = ({ onReady }) => { })); }; - const fetchData = useCallback(async ({ showLoader = true } = {}) => { - if (showLoader) { - setIsLoading(true); - } - - const params = new URLSearchParams(); - params.append('page', pagination.pageIndex + 1); - params.append('page_size', pagination.pageSize); - - // Apply sorting - if (sorting.length > 0) { - const columnId = sorting[0].id; - // Map frontend column IDs to backend field names - const fieldMapping = { - name: 'name', - group: 'channel_group__name', - m3u: 'm3u_account__name', - }; - const sortField = fieldMapping[columnId] || columnId; - const sortDirection = sorting[0].desc ? '-' : ''; - params.append('ordering', `${sortDirection}${sortField}`); - } - - // Apply debounced filters - Object.entries(debouncedFilters).forEach(([key, value]) => { - if (value) params.append(key, value); - }); - - try { - const [result, ids, filterOptions] = await Promise.all([ - API.queryStreamsTable(params), - API.getAllStreamIds(params), - API.getStreamFilterOptions(params), - ]); - - setAllRowIds(ids); - - // Set filtered options based on current filters - setGroupOptions(filterOptions.groups); - setM3uOptions( - filterOptions.m3u_accounts.map((m3u) => ({ - label: m3u.name, - value: `${m3u.id}`, - })) - ); - - if (initialDataCount === null) { - setInitialDataCount(result.count); + const fetchData = useCallback( + async ({ showLoader = true } = {}) => { + if (showLoader) { + setIsLoading(true); } - // Signal that initial data load is complete - if (!hasSignaledReady.current && onReady) { - hasSignaledReady.current = true; - onReady(); - } - } catch (error) { - console.error('Error fetching data:', error); - } + const params = new URLSearchParams(); + params.append('page', pagination.pageIndex + 1); + params.append('page_size', pagination.pageSize); - hasFetchedOnce.current = true; - if (showLoader) { - setIsLoading(false); - } - }, [pagination, sorting, debouncedFilters, onReady]); + // Apply sorting + if (sorting.length > 0) { + const columnId = sorting[0].id; + // Map frontend column IDs to backend field names + const fieldMapping = { + name: 'name', + group: 'channel_group__name', + m3u: 'm3u_account__name', + }; + const sortField = fieldMapping[columnId] || columnId; + const sortDirection = sorting[0].desc ? '-' : ''; + params.append('ordering', `${sortDirection}${sortField}`); + } + + // Apply debounced filters + Object.entries(debouncedFilters).forEach(([key, value]) => { + if (value) params.append(key, value); + }); + + try { + const [result, ids, filterOptions] = await Promise.all([ + API.queryStreamsTable(params), + API.getAllStreamIds(params), + API.getStreamFilterOptions(params), + ]); + + setAllRowIds(ids); + + // Set filtered options based on current filters + setGroupOptions(filterOptions.groups); + setM3uOptions( + filterOptions.m3u_accounts.map((m3u) => ({ + label: m3u.name, + value: `${m3u.id}`, + })) + ); + + if (initialDataCount === null) { + setInitialDataCount(result.count); + } + + // Signal that initial data load is complete + if (!hasSignaledReady.current && onReady) { + hasSignaledReady.current = true; + onReady(); + } + } catch (error) { + console.error('Error fetching data:', error); + } + + hasFetchedOnce.current = true; + if (showLoader) { + setIsLoading(false); + } + }, + [pagination, sorting, debouncedFilters, onReady] + ); // Bulk creation: create channels from selected streams asynchronously const createChannelsFromStreams = async () => {