advanced filtering for hiding disabled channels and viewing only empty channels

This commit is contained in:
dekzter 2025-12-11 11:54:41 -05:00
parent 0bfd06a5a3
commit ea38c0b4b8
3 changed files with 78 additions and 23 deletions

View file

@ -8,6 +8,7 @@ from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi from drf_yasg import openapi
from django.shortcuts import get_object_or_404, get_list_or_404 from django.shortcuts import get_object_or_404, get_list_or_404
from django.db import transaction from django.db import transaction
from django.db.models import Q
import os, json, requests, logging import os, json, requests, logging
from apps.accounts.permissions import ( from apps.accounts.permissions import (
Authenticated, Authenticated,
@ -419,31 +420,36 @@ class ChannelViewSet(viewsets.ModelViewSet):
group_names = channel_group.split(",") group_names = channel_group.split(",")
qs = qs.filter(channel_group__name__in=group_names) qs = qs.filter(channel_group__name__in=group_names)
filters = {}
q_filters = Q()
channel_profile_id = self.request.query_params.get("channel_profile_id") channel_profile_id = self.request.query_params.get("channel_profile_id")
show_disabled_param = self.request.query_params.get("show_disabled", None) show_disabled_param = self.request.query_params.get("show_disabled", None)
only_streamless = self.request.query_params.get("only_streamless", None)
if channel_profile_id: if channel_profile_id:
try: try:
profile_id_int = int(channel_profile_id) profile_id_int = int(channel_profile_id)
# If show_disabled is present, include all memberships for that profile. filters["channelprofilemembership__channel_profile_id"] = profile_id_int
# If absent, restrict to enabled=True.
if show_disabled_param is None: if show_disabled_param is None:
qs = qs.filter( filters["channelprofilemembership__enabled"] = True
channelprofilemembership__channel_profile_id=profile_id_int,
channelprofilemembership__enabled=True,
)
else:
qs = qs.filter(
channelprofilemembership__channel_profile_id=profile_id_int
)
except (ValueError, TypeError): except (ValueError, TypeError):
# Ignore invalid profile id values # Ignore invalid profile id values
pass pass
if self.request.user.user_level < 10: if only_streamless:
qs = qs.filter(user_level__lte=self.request.user.user_level) q_filters &= Q(streams__isnull=True)
return qs if self.request.user.user_level < 10:
filters["user_level__lte"] = self.request.user.user_level
if filters:
qs = qs.filter(**filters)
if q_filters:
qs = qs.filter(q_filters)
return qs.distinct()
def get_serializer_context(self): def get_serializer_context(self):
context = super().get_serializer_context() context = super().get_serializer_context()

View file

@ -294,6 +294,8 @@ const ChannelsTable = ({}) => {
profiles[selectedProfileId] profiles[selectedProfileId]
); );
const [showDisabled, setShowDisabled] = useState(true); const [showDisabled, setShowDisabled] = useState(true);
const [showOnlyStreamlessChannels, setShowOnlyStreamlessChannels] =
useState(false);
const [paginationString, setPaginationString] = useState(''); const [paginationString, setPaginationString] = useState('');
const [filters, setFilters] = useState({ const [filters, setFilters] = useState({
@ -380,6 +382,9 @@ const ChannelsTable = ({}) => {
if (showDisabled === true) { if (showDisabled === true) {
params.append('show_disabled', true); params.append('show_disabled', true);
} }
if (showOnlyStreamlessChannels === true) {
params.append('only_streamless', true);
}
// Apply sorting // Apply sorting
if (sorting.length > 0) { if (sorting.length > 0) {
@ -412,7 +417,14 @@ const ChannelsTable = ({}) => {
pageSize: pagination.pageSize, pageSize: pagination.pageSize,
}); });
setAllRowIds(ids); setAllRowIds(ids);
}, [pagination, sorting, debouncedFilters, showDisabled, selectedProfileId]); }, [
pagination,
sorting,
debouncedFilters,
showDisabled,
selectedProfileId,
showOnlyStreamlessChannels,
]);
const stopPropagation = useCallback((e) => { const stopPropagation = useCallback((e) => {
e.stopPropagation(); e.stopPropagation();
@ -1340,6 +1352,8 @@ const ChannelsTable = ({}) => {
table={table} table={table}
showDisabled={showDisabled} showDisabled={showDisabled}
setShowDisabled={setShowDisabled} setShowDisabled={setShowDisabled}
showOnlyStreamlessChannels={showOnlyStreamlessChannels}
setShowOnlyStreamlessChannels={setShowOnlyStreamlessChannels}
/> />
{/* Table or ghost empty state inside Paper */} {/* Table or ghost empty state inside Paper */}

View file

@ -12,15 +12,12 @@ import {
Text, Text,
TextInput, TextInput,
Tooltip, Tooltip,
UnstyledButton,
useMantineTheme, useMantineTheme,
} from '@mantine/core'; } from '@mantine/core';
import { import {
ArrowDown01, ArrowDown01,
Binary, Binary,
Check,
CircleCheck, CircleCheck,
Ellipsis,
EllipsisVertical, EllipsisVertical,
SquareMinus, SquareMinus,
SquarePen, SquarePen,
@ -28,6 +25,9 @@ import {
Settings, Settings,
Eye, Eye,
EyeOff, EyeOff,
Filter,
Square,
SquareCheck,
} from 'lucide-react'; } from 'lucide-react';
import API from '../../../api'; import API from '../../../api';
import { notifications } from '@mantine/notifications'; import { notifications } from '@mantine/notifications';
@ -106,6 +106,8 @@ const ChannelTableHeader = ({
selectedTableIds, selectedTableIds,
showDisabled, showDisabled,
setShowDisabled, setShowDisabled,
showOnlyStreamlessChannels,
setShowOnlyStreamlessChannels,
}) => { }) => {
const theme = useMantineTheme(); const theme = useMantineTheme();
@ -216,6 +218,10 @@ const ChannelTableHeader = ({
setShowDisabled(!showDisabled); setShowDisabled(!showDisabled);
}; };
const toggleShowOnlyStreamlessChannels = () => {
setShowOnlyStreamlessChannels(!showOnlyStreamlessChannels);
};
return ( return (
<Group justify="space-between"> <Group justify="space-between">
<Group gap={5} style={{ paddingLeft: 10 }}> <Group gap={5} style={{ paddingLeft: 10 }}>
@ -234,12 +240,6 @@ const ChannelTableHeader = ({
<Tooltip label="Create Profile"> <Tooltip label="Create Profile">
<CreateProfilePopover /> <CreateProfilePopover />
</Tooltip> </Tooltip>
<Tooltip label={showDisabled ? 'Hide Disabled' : 'Show Disabled'}>
<Button size="xs" variant="default" onClick={toggleShowDisabled}>
{showDisabled ? <Eye size={18} /> : <EyeOff size={18} />}
</Button>
</Tooltip>
</Group> </Group>
<Box <Box
@ -250,6 +250,41 @@ const ChannelTableHeader = ({
}} }}
> >
<Flex gap={6}> <Flex gap={6}>
<Menu shadow="md" width={200}>
<Menu.Target>
<Button size="xs" variant="default" onClick={() => {}}>
<Filter size={18} />
</Button>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item
onClick={toggleShowDisabled}
leftSection={
showDisabled ? <Eye size={18} /> : <EyeOff size={18} />
}
disabled={selectedProfileId === '0'}
>
<Text size="xs">
{showDisabled ? 'Hide Disabled' : 'Show Disabled'}
</Text>
</Menu.Item>
<Menu.Item
onClick={toggleShowOnlyStreamlessChannels}
leftSection={
showOnlyStreamlessChannels ? (
<SquareCheck size={18} />
) : (
<Square size={18} />
)
}
>
<Text size="xs">Only Empty Channels</Text>
</Menu.Item>
</Menu.Dropdown>
</Menu>
<Button <Button
leftSection={<SquarePen size={18} />} leftSection={<SquarePen size={18} />}
variant="default" variant="default"