Fixes bulk channel editor not saving.

Fixes #222
This commit is contained in:
SergeantPanda 2025-06-26 13:15:00 -05:00
parent 65e0be80e0
commit ba6012b28c
3 changed files with 110 additions and 32 deletions

View file

@ -8,7 +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
import os, json, requests
import os, json, requests, logging
from apps.accounts.permissions import (
Authenticated,
IsAdmin,
@ -48,6 +48,9 @@ import mimetypes
from rest_framework.pagination import PageNumberPagination
logger = logging.getLogger(__name__)
class OrInFilter(django_filters.Filter):
"""
Custom filter that handles the OR condition instead of AND.
@ -275,30 +278,76 @@ class ChannelViewSet(viewsets.ModelViewSet):
@action(detail=False, methods=["patch"], url_path="edit/bulk")
def edit_bulk(self, request):
data_list = request.data
if not isinstance(data_list, list):
"""
Bulk edit channels.
Expects a list of channels with their updates.
"""
data = request.data
if not isinstance(data, list):
return Response(
{"error": "Expected a list of channel objects objects"},
{"error": "Expected a list of channel updates"},
status=status.HTTP_400_BAD_REQUEST,
)
updated_channels = []
try:
with transaction.atomic():
for item in data_list:
channel = Channel.objects.id(id=item.pop("id"))
for key, value in item.items():
setattr(channel, key, value)
errors = []
channel.save(update_fields=item.keys())
updated_channels.append(channel)
except Exception as e:
logger.error("Error during bulk channel edit", e)
return Response({"error": e}, status=500)
for channel_data in data:
channel_id = channel_data.get("id")
if not channel_id:
errors.append({"error": "Channel ID is required"})
continue
response_data = ChannelSerializer(updated_channels, many=True).data
try:
channel = Channel.objects.get(id=channel_id)
return Response(response_data, status=status.HTTP_200_OK)
# Handle channel_group_id properly - convert string to integer if needed
if 'channel_group_id' in channel_data:
group_id = channel_data['channel_group_id']
if group_id is not None:
try:
channel_data['channel_group_id'] = int(group_id)
except (ValueError, TypeError):
channel_data['channel_group_id'] = None
# Use the serializer to validate and update
serializer = ChannelSerializer(
channel, data=channel_data, partial=True
)
if serializer.is_valid():
updated_channel = serializer.save()
updated_channels.append(updated_channel)
else:
errors.append({
"channel_id": channel_id,
"errors": serializer.errors
})
except Channel.DoesNotExist:
errors.append({
"channel_id": channel_id,
"error": "Channel not found"
})
except Exception as e:
errors.append({
"channel_id": channel_id,
"error": str(e)
})
if errors:
return Response(
{"errors": errors, "updated_count": len(updated_channels)},
status=status.HTTP_400_BAD_REQUEST,
)
# Serialize the updated channels for response
serialized_channels = ChannelSerializer(updated_channels, many=True).data
return Response({
"message": f"Successfully updated {len(updated_channels)} channels",
"channels": serialized_channels
})
@action(detail=False, methods=["get"], url_path="ids")
def get_ids(self, request, *args, **kwargs):

View file

@ -390,9 +390,9 @@ export default class API {
static async updateChannels(ids, values) {
const body = [];
for (const id in ids) {
for (const id of ids) {
body.push({
id,
id: id,
...values,
});
}
@ -406,7 +406,10 @@ export default class API {
}
);
useChannelsStore.getState().updateChannels(response);
// Pass the channels array from the response, not the entire response
if (response.channels) {
useChannelsStore.getState().updateChannels(response.channels);
}
return response;
} catch (e) {
errorNotification('Failed to update channels', e);

View file

@ -36,6 +36,7 @@ const ChannelBatchForm = ({ channelIds, isOpen, onClose }) => {
const [channelGroupModelOpen, setChannelGroupModalOpen] = useState(false);
const [selectedChannelGroup, setSelectedChannelGroup] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [groupPopoverOpened, setGroupPopoverOpened] = useState(false);
const [groupFilter, setGroupFilter] = useState('');
@ -51,27 +52,38 @@ const ChannelBatchForm = ({ channelIds, isOpen, onClose }) => {
});
const onSubmit = async () => {
setIsSubmitting(true);
const values = {
...form.getValues(),
channel_group_id: selectedChannelGroup,
};
// Handle channel group ID - convert to integer if it exists
if (selectedChannelGroup) {
values.channel_group_id = parseInt(selectedChannelGroup);
} else {
delete values.channel_group_id;
}
if (!values.stream_profile_id || values.stream_profile_id === '0') {
values.stream_profile_id = null;
}
if (!values.channel_group_id) {
delete values.channel_group_id;
}
if (values.user_level == '-1') {
delete values.user_level;
}
await API.batchUpdateChannels({
ids: channelIds,
values,
});
// Remove the channel_group field from form values as we use channel_group_id
delete values.channel_group;
try {
await API.updateChannels(channelIds, values);
onClose();
} catch (error) {
console.error('Failed to update channels:', error);
} finally {
setIsSubmitting(false);
}
};
// useEffect(() => {
@ -151,6 +163,21 @@ const ChannelBatchForm = ({ channelIds, isOpen, onClose }) => {
onClick={() => setGroupPopoverOpened(true)}
size="xs"
style={{ flex: 1 }}
rightSection={
form.getValues().channel_group && (
<ActionIcon
size="xs"
variant="subtle"
onClick={(e) => {
e.stopPropagation();
setSelectedChannelGroup('');
form.setValues({ channel_group: '' });
}}
>
<X size={12} />
</ActionIcon>
)
}
/>
<ActionIcon
@ -264,7 +291,7 @@ const ChannelBatchForm = ({ channelIds, isOpen, onClose }) => {
label: '(no change)',
},
].concat(
Object.entries(USER_LEVELS).map(([label, value]) => {
Object.entries(USER_LEVELS).map(([, value]) => {
return {
label: USER_LEVEL_LABELS[value],
value: `${value}`,
@ -274,9 +301,8 @@ const ChannelBatchForm = ({ channelIds, isOpen, onClose }) => {
/>
</Stack>
</Group>
<Flex mih={50} gap="xs" justify="flex-end" align="flex-end">
<Button type="submit" variant="default" disabled={form.submitting}>
<Button type="submit" variant="default" disabled={isSubmitting}>
Submit
</Button>
</Flex>