diff --git a/apps/channels/api_views.py b/apps/channels/api_views.py
index 0106ffd5..b651081e 100644
--- a/apps/channels/api_views.py
+++ b/apps/channels/api_views.py
@@ -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):
diff --git a/frontend/src/api.js b/frontend/src/api.js
index 17c38b90..cd9d21ca 100644
--- a/frontend/src/api.js
+++ b/frontend/src/api.js
@@ -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);
diff --git a/frontend/src/components/forms/ChannelBatch.jsx b/frontend/src/components/forms/ChannelBatch.jsx
index 12ee52b8..e26dba38 100644
--- a/frontend/src/components/forms/ChannelBatch.jsx
+++ b/frontend/src/components/forms/ChannelBatch.jsx
@@ -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 && (
+ {
+ e.stopPropagation();
+ setSelectedChannelGroup('');
+ form.setValues({ channel_group: '' });
+ }}
+ >
+
+
+ )
+ }
/>
{
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 }) => {
/>
-
-