diff --git a/apps/m3u/models.py b/apps/m3u/models.py index a9ef1126..eef76527 100644 --- a/apps/m3u/models.py +++ b/apps/m3u/models.py @@ -20,6 +20,7 @@ class M3UAccount(models.Model): PARSING = "parsing", "Parsing" ERROR = "error", "Error" SUCCESS = "success", "Success" + PENDING_SETUP = "pending_setup", "Pending Setup" """Represents an M3U Account for IPTV streams.""" name = models.CharField( diff --git a/apps/m3u/signals.py b/apps/m3u/signals.py index dc96ed57..c78328cf 100644 --- a/apps/m3u/signals.py +++ b/apps/m3u/signals.py @@ -28,17 +28,10 @@ def create_or_update_refresh_task(sender, instance, **kwargs): period=IntervalSchedule.HOURS ) - if not instance.refresh_task: - refresh_task = PeriodicTask.objects.create( - name=task_name, - interval=interval, - task="apps.m3u.tasks.refresh_single_m3u_account", - kwargs=json.dumps({"account_id": instance.id}), - enabled=instance.refresh_interval != 0, - ) - M3UAccount.objects.filter(id=instance.id).update(refresh_task=refresh_task) - else: - task = instance.refresh_task + # First check if the task already exists to avoid validation errors + try: + task = PeriodicTask.objects.get(name=task_name) + # Task exists, just update it updated_fields = [] if task.enabled != (instance.refresh_interval != 0): @@ -52,6 +45,21 @@ def create_or_update_refresh_task(sender, instance, **kwargs): if updated_fields: task.save(update_fields=updated_fields) + # Ensure instance has the task + if instance.refresh_task_id != task.id: + M3UAccount.objects.filter(id=instance.id).update(refresh_task=task) + + except PeriodicTask.DoesNotExist: + # Create new task if it doesn't exist + refresh_task = PeriodicTask.objects.create( + name=task_name, + interval=interval, + task="apps.m3u.tasks.refresh_single_m3u_account", + kwargs=json.dumps({"account_id": instance.id}), + enabled=instance.refresh_interval != 0, + ) + M3UAccount.objects.filter(id=instance.id).update(refresh_task=refresh_task) + @receiver(post_delete, sender=M3UAccount) def delete_refresh_task(sender, instance, **kwargs): """ diff --git a/apps/m3u/tasks.py b/apps/m3u/tasks.py index db069947..08410497 100644 --- a/apps/m3u/tasks.py +++ b/apps/m3u/tasks.py @@ -547,7 +547,13 @@ def refresh_m3u_groups(account_id, use_cache=False, full_refresh=False): release_task_lock('refresh_m3u_account_groups', account_id) - send_m3u_update(account_id, "processing_groups", 100) + # Use update() instead of save() to avoid triggering signals + M3UAccount.objects.filter(id=account_id).update( + status=M3UAccount.Status.PENDING_SETUP, + last_message="M3U groups loaded. Please select groups or refresh M3U to complete setup." + ) + + send_m3u_update(account_id, "processing_groups", 100, status="pending_setup", message="M3U groups loaded. Please select groups or refresh M3U to complete setup.") if not full_refresh: channel_layer = get_channel_layer() diff --git a/frontend/src/WebSocket.jsx b/frontend/src/WebSocket.jsx index 8dff3ae9..7f1ba5c7 100644 --- a/frontend/src/WebSocket.jsx +++ b/frontend/src/WebSocket.jsx @@ -190,8 +190,8 @@ export const WebsocketProvider = ({ children }) => { // Update the store with progress information setRefreshProgress(parsedEvent.data); - // If complete (progress is 100%), also update the playlist status - if (parsedEvent.data.progress === 100 && parsedEvent.data.account) { + // Always update status for pending_setup, not just when progress is 100% + if ((parsedEvent.data.progress === 100 || parsedEvent.data.status === "pending_setup") && parsedEvent.data.account) { const playlistsState = usePlaylistsStore.getState(); const playlist = playlistsState.playlists.find(p => p.id === parsedEvent.data.account); diff --git a/frontend/src/components/M3URefreshNotification.jsx b/frontend/src/components/M3URefreshNotification.jsx index 5eefeef0..99c5a50d 100644 --- a/frontend/src/components/M3URefreshNotification.jsx +++ b/frontend/src/components/M3URefreshNotification.jsx @@ -35,6 +35,17 @@ export default function M3URefreshNotification() { [data.account]: data, }); + // Special handling for pending setup status + if (data.status === "pending_setup") { + notifications.show({ + title: `M3U Setup: ${playlist.name}`, + message: data.message || "M3U groups loaded. Please select groups or refresh M3U to complete setup.", + color: 'orange.5', + autoClose: 5000, // Keep visible a bit longer + }); + return; + } + // Check for error status FIRST before doing anything else if (data.status === "error") { // Only show the error notification if we have a complete task (progress=100) diff --git a/frontend/src/components/tables/M3UsTable.jsx b/frontend/src/components/tables/M3UsTable.jsx index cb66bf90..d21af941 100644 --- a/frontend/src/components/tables/M3UsTable.jsx +++ b/frontend/src/components/tables/M3UsTable.jsx @@ -33,6 +33,7 @@ const formatStatusText = (status) => { case 'parsing': return 'Parsing'; case 'error': return 'Error'; case 'success': return 'Success'; + case 'pending_setup': return '⚠️ Pending Setup'; default: return status ? status.charAt(0).toUpperCase() + status.slice(1) : 'Unknown'; } }; @@ -45,6 +46,7 @@ const getStatusColor = (status) => { case 'parsing': return 'indigo.5'; case 'error': return 'red.5'; case 'success': return 'green.5'; + case 'pending_setup': return 'orange.5'; // Orange to indicate action needed default: return 'gray.5'; } };