Add ability to cleanup all unused groups.

This commit is contained in:
SergeantPanda 2025-07-12 17:05:48 -05:00
parent adc6604fa2
commit 9b7aa0c894
3 changed files with 93 additions and 9 deletions

View file

@ -198,47 +198,78 @@ class ChannelGroupViewSet(viewsets.ModelViewSet):
def update(self, request, *args, **kwargs):
"""Override update to check M3U associations"""
instance = self.get_object()
# Check if group has M3U account associations
if hasattr(instance, 'm3u_account') and instance.m3u_account.exists():
return Response(
{"error": "Cannot edit group with M3U account associations"},
status=status.HTTP_400_BAD_REQUEST
)
return super().update(request, *args, **kwargs)
def partial_update(self, request, *args, **kwargs):
"""Override partial_update to check M3U associations"""
instance = self.get_object()
# Check if group has M3U account associations
if hasattr(instance, 'm3u_account') and instance.m3u_account.exists():
return Response(
{"error": "Cannot edit group with M3U account associations"},
status=status.HTTP_400_BAD_REQUEST
)
return super().partial_update(request, *args, **kwargs)
@swagger_auto_schema(
method="post",
operation_description="Delete all channel groups that have no associations (no channels or M3U accounts)",
responses={200: "Cleanup completed"},
)
@action(detail=False, methods=["post"], url_path="cleanup")
def cleanup_unused_groups(self, request):
"""Delete all channel groups with no channels or M3U account associations"""
from django.db.models import Count
# Find groups with no channels and no M3U account associations
unused_groups = ChannelGroup.objects.annotate(
channel_count=Count('channels', distinct=True),
m3u_account_count=Count('m3u_account', distinct=True)
).filter(
channel_count=0,
m3u_account_count=0
)
deleted_count = unused_groups.count()
group_names = list(unused_groups.values_list('name', flat=True))
# Delete the unused groups
unused_groups.delete()
return Response({
"message": f"Successfully deleted {deleted_count} unused channel groups",
"deleted_count": deleted_count,
"deleted_groups": group_names
})
def destroy(self, request, *args, **kwargs):
"""Override destroy to check for associations before deletion"""
instance = self.get_object()
# Check if group has associated channels
if instance.channels.exists():
return Response(
{"error": "Cannot delete group with associated channels"},
status=status.HTTP_400_BAD_REQUEST
)
# Check if group has M3U account associations
if hasattr(instance, 'm3u_account') and instance.m3u_account.exists():
return Response(
{"error": "Cannot delete group with M3U account associations"},
status=status.HTTP_400_BAD_REQUEST
)
return super().destroy(request, *args, **kwargs)

View file

@ -301,6 +301,22 @@ export default class API {
}
}
static async cleanupUnusedChannelGroups() {
try {
const response = await request(`${host}/api/channels/groups/cleanup/`, {
method: 'POST',
});
// Refresh channel groups to update the UI
useChannelsStore.getState().fetchChannelGroups();
return response;
} catch (e) {
errorNotification('Failed to cleanup unused channel groups', e);
throw e;
}
}
static async addChannel(channel) {
try {
let body = null;

View file

@ -21,7 +21,8 @@ import {
X,
AlertCircle,
Database,
Tv
Tv,
Trash
} from 'lucide-react';
import { notifications } from '@mantine/notifications';
import useChannelsStore from '../../store/channels';
@ -135,6 +136,7 @@ const GroupManager = React.memo(({ isOpen, onClose }) => {
const [groupUsage, setGroupUsage] = useState({});
const [loading, setLoading] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [isCleaningUp, setIsCleaningUp] = useState(false);
// Memoize the channel groups array to prevent unnecessary re-renders
const channelGroupsArray = useMemo(() =>
@ -305,6 +307,29 @@ const GroupManager = React.memo(({ isOpen, onClose }) => {
setSearchTerm(e.target.value);
}, []);
const handleCleanup = useCallback(async () => {
setIsCleaningUp(true);
try {
const result = await API.cleanupUnusedChannelGroups();
notifications.show({
title: 'Cleanup Complete',
message: `Successfully deleted ${result.deleted_count} unused groups`,
color: 'green',
});
fetchGroupUsage(); // Refresh usage data
} catch (error) {
notifications.show({
title: 'Cleanup Failed',
message: 'Failed to cleanup unused groups',
color: 'red',
});
} finally {
setIsCleaningUp(false);
}
}, [fetchGroupUsage]);
if (!isOpen) return null;
return (
@ -322,7 +347,19 @@ const GroupManager = React.memo(({ isOpen, onClose }) => {
{/* Create new group section */}
<Stack>
<Text size="sm" fw={600}>Create New Group</Text>
<Group justify="space-between" align="center">
<Text size="sm" fw={600}>Create New Group</Text>
<Button
leftSection={<Trash size={16} />}
variant="light"
size="sm"
color="orange"
onClick={handleCleanup}
loading={isCleaningUp}
>
Cleanup Unused
</Button>
</Group>
<Group>
{isCreating ? (
<>