From d1ac5b11e5dc649228761400f97f04a43eeb62c8 Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Sat, 4 Oct 2025 15:20:35 -0500 Subject: [PATCH] Add EPG TVG-ID setting functionality - Implemented API endpoint to set channel TVG-IDs from EPG data. - Created Celery task to handle TVG-ID updates for multiple channels. - Added frontend methods to initiate TVG-ID setting from EPG for both single and batch channel forms. - Enhanced notifications for task status updates. --- apps/channels/api_views.py | 31 ++++++ apps/channels/tasks.py | 95 +++++++++++++++++++ frontend/src/api.js | 23 +++++ frontend/src/components/forms/Channel.jsx | 46 ++++++++- .../src/components/forms/ChannelBatch.jsx | 44 ++++++++- 5 files changed, 237 insertions(+), 2 deletions(-) diff --git a/apps/channels/api_views.py b/apps/channels/api_views.py index 7a3d5135..f5395c4f 100644 --- a/apps/channels/api_views.py +++ b/apps/channels/api_views.py @@ -555,6 +555,37 @@ class ChannelViewSet(viewsets.ModelViewSet): "channel_count": len(channel_ids) }) + @action(detail=False, methods=["post"], url_path="set-tvg-ids-from-epg") + def set_tvg_ids_from_epg(self, request): + """ + Trigger a Celery task to set channel TVG-IDs from EPG data + """ + from .tasks import set_channels_tvg_ids_from_epg + + data = request.data + channel_ids = data.get("channel_ids", []) + + if not channel_ids: + return Response( + {"error": "channel_ids is required"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + if not isinstance(channel_ids, list): + return Response( + {"error": "channel_ids must be a list"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + # Start the Celery task + task = set_channels_tvg_ids_from_epg.delay(channel_ids) + + return Response({ + "message": f"Started EPG TVG-ID setting task for {len(channel_ids)} channels", + "task_id": task.id, + "channel_count": len(channel_ids) + }) + @action(detail=False, methods=["get"], url_path="ids") def get_ids(self, request, *args, **kwargs): # Get the filtered queryset diff --git a/apps/channels/tasks.py b/apps/channels/tasks.py index 732d03cc..51cb884c 100755 --- a/apps/channels/tasks.py +++ b/apps/channels/tasks.py @@ -2711,3 +2711,98 @@ def set_channels_logos_from_epg(self, channel_ids): 'error': str(e) }) raise + + +@shared_task(bind=True) +def set_channels_tvg_ids_from_epg(self, channel_ids): + """ + Celery task to set channel TVG-IDs from EPG data for multiple channels + """ + from core.utils import send_websocket_update + + task_id = self.request.id + total_channels = len(channel_ids) + updated_count = 0 + errors = [] + + try: + logger.info(f"Starting EPG TVG-ID setting task for {total_channels} channels") + + # Send initial progress + send_websocket_update('updates', 'update', { + 'type': 'epg_tvg_id_setting_progress', + 'task_id': task_id, + 'progress': 0, + 'total': total_channels, + 'status': 'running', + 'message': 'Starting EPG TVG-ID setting...' + }) + + batch_size = 100 + for i in range(0, total_channels, batch_size): + batch_ids = channel_ids[i:i + batch_size] + batch_updates = [] + + # Get channels and their EPG data + channels = Channel.objects.filter(id__in=batch_ids).select_related('epg_data') + + for channel in channels: + try: + if channel.epg_data and channel.epg_data.tvg_id: + if channel.tvg_id != channel.epg_data.tvg_id: + channel.tvg_id = channel.epg_data.tvg_id + batch_updates.append(channel) + updated_count += 1 + except Exception as e: + errors.append(f"Channel {channel.id}: {str(e)}") + logger.error(f"Error processing channel {channel.id}: {e}") + + # Bulk update the batch + if batch_updates: + Channel.objects.bulk_update(batch_updates, ['tvg_id']) + + # Send progress update + progress = min(i + batch_size, total_channels) + send_websocket_update('updates', 'update', { + 'type': 'epg_tvg_id_setting_progress', + 'task_id': task_id, + 'progress': progress, + 'total': total_channels, + 'status': 'running', + 'message': f'Updated {updated_count} channel TVG-IDs...', + 'updated_count': updated_count + }) + + # Send completion notification + send_websocket_update('updates', 'update', { + 'type': 'epg_tvg_id_setting_progress', + 'task_id': task_id, + 'progress': total_channels, + 'total': total_channels, + 'status': 'completed', + 'message': f'Successfully updated {updated_count} channel TVG-IDs from EPG data', + 'updated_count': updated_count, + 'error_count': len(errors), + 'errors': errors + }) + + logger.info(f"EPG TVG-ID setting task completed. Updated {updated_count} channels") + return { + 'status': 'completed', + 'updated_count': updated_count, + 'error_count': len(errors), + 'errors': errors + } + + except Exception as e: + logger.error(f"EPG TVG-ID setting task failed: {e}") + send_websocket_update('updates', 'update', { + 'type': 'epg_tvg_id_setting_progress', + 'task_id': task_id, + 'progress': 0, + 'total': total_channels, + 'status': 'failed', + 'message': f'Task failed: {str(e)}', + 'error': str(e) + }) + raise diff --git a/frontend/src/api.js b/frontend/src/api.js index 01186bf6..fcd2b6f4 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -562,6 +562,29 @@ export default class API { } } + static async setChannelTvgIdsFromEpg(channelIds) { + try { + const response = await request( + `${host}/api/channels/channels/set-tvg-ids-from-epg/`, + { + method: 'POST', + body: { channel_ids: channelIds }, + } + ); + + notifications.show({ + title: 'Task Started', + message: response.message, + color: 'blue', + }); + + return response; + } catch (e) { + errorNotification('Failed to start EPG TVG-ID setting task', e); + throw e; + } + } + static async assignChannelNumbers(channelIds, startingNum = 1) { try { const response = await request(`${host}/api/channels/channels/assign/`, { diff --git a/frontend/src/components/forms/Channel.jsx b/frontend/src/components/forms/Channel.jsx index b4c494e1..fd2e5312 100644 --- a/frontend/src/components/forms/Channel.jsx +++ b/frontend/src/components/forms/Channel.jsx @@ -263,6 +263,34 @@ const ChannelForm = ({ channel = null, isOpen, onClose }) => { } }; + const handleSetTvgIdFromEpg = () => { + const epgDataId = formik.values.epg_data_id; + if (!epgDataId) { + notifications.show({ + title: 'No EPG Selected', + message: 'Please select an EPG source first.', + color: 'orange', + }); + return; + } + + const tvg = tvgsById[epgDataId]; + if (tvg && tvg.tvg_id) { + formik.setFieldValue('tvg_id', tvg.tvg_id); + notifications.show({ + title: 'Success', + message: `TVG-ID set to "${tvg.tvg_id}"`, + color: 'green', + }); + } else { + notifications.show({ + title: 'No TVG-ID Available', + message: 'No TVG-ID found in the selected EPG data.', + color: 'orange', + }); + } + }; + const formik = useFormik({ initialValues: { name: '', @@ -823,7 +851,23 @@ const ChannelForm = ({ channel = null, isOpen, onClose }) => { + TVG-ID + {formik.values.epg_data_id && ( + + )} + + } value={formik.values.tvg_id} onChange={formik.handleChange} error={formik.errors.tvg_id ? formik.touched.tvg_id : ''} diff --git a/frontend/src/components/forms/ChannelBatch.jsx b/frontend/src/components/forms/ChannelBatch.jsx index ad61fb26..5c0c1cce 100644 --- a/frontend/src/components/forms/ChannelBatch.jsx +++ b/frontend/src/components/forms/ChannelBatch.jsx @@ -202,6 +202,40 @@ const ChannelBatchForm = ({ channelIds, isOpen, onClose }) => { } }; + const handleSetTvgIdsFromEpg = async () => { + if (!channelIds || channelIds.length === 0) { + notifications.show({ + title: 'No Channels Selected', + message: 'No channels to update.', + color: 'orange', + }); + return; + } + + try { + // Start the backend task + await API.setChannelTvgIdsFromEpg(channelIds); + + // The task will send WebSocket updates for progress + // Just show that it started successfully + notifications.show({ + title: 'Task Started', + message: `Started setting TVG-IDs from EPG for ${channelIds.length} channels. Progress will be shown in notifications.`, + color: 'blue', + }); + + // Close the modal since the task is now running in background + onClose(); + } catch (error) { + console.error('Failed to start EPG TVG-ID setting task:', error); + notifications.show({ + title: 'Error', + message: 'Failed to start EPG TVG-ID setting task.', + color: 'red', + }); + } + }; + // useEffect(() => { // // const sameStreamProfile = channels.every( // // (channel) => channel.stream_profile_id == channels[0].stream_profile_id @@ -317,9 +351,17 @@ const ChannelBatchForm = ({ channelIds, isOpen, onClose }) => { > Set Logos from EPG + - Updates channel names and logos based on their assigned EPG + Updates channel names, logos, and TVG-IDs based on their assigned EPG data