diff --git a/apps/channels/api_views.py b/apps/channels/api_views.py index ee7109b7..dbdd4271 100644 --- a/apps/channels/api_views.py +++ b/apps/channels/api_views.py @@ -1053,24 +1053,27 @@ class BulkDeleteLogosAPIView(APIView): def delete(self, request): logo_ids = request.data.get("logo_ids", []) - # Check if any logos are being used by channels - used_logos = Logo.objects.filter( - id__in=logo_ids, - channels__isnull=False - ).distinct() + # Get logos and their usage info before deletion + logos_to_delete = Logo.objects.filter(id__in=logo_ids) + total_channels_affected = 0 + + for logo in logos_to_delete: + if logo.channels.exists(): + channel_count = logo.channels.count() + total_channels_affected += channel_count + # Remove logo from channels + logo.channels.update(logo=None) + logger.info(f"Removed logo {logo.name} from {channel_count} channels before deletion") - if used_logos.exists(): - used_names = list(used_logos.values_list('name', flat=True)) - return Response( - {"error": f"Cannot delete logos that are in use: {', '.join(used_names)}"}, - status=status.HTTP_400_BAD_REQUEST - ) + # Delete logos + deleted_count = logos_to_delete.delete()[0] - # Delete logos that are not in use - deleted_count = Logo.objects.filter(id__in=logo_ids).delete()[0] + message = f"Successfully deleted {deleted_count} logos" + if total_channels_affected > 0: + message += f" and removed them from {total_channels_affected} channels" return Response( - {"message": f"Successfully deleted {deleted_count} logos"}, + {"message": message}, status=status.HTTP_204_NO_CONTENT ) @@ -1152,15 +1155,14 @@ class LogoViewSet(viewsets.ModelViewSet): return super().update(request, *args, **kwargs) def destroy(self, request, *args, **kwargs): - """Delete a logo""" + """Delete a logo and remove it from any channels using it""" logo = self.get_object() - # Check if logo is being used by any channels + # Instead of preventing deletion, remove the logo from channels if logo.channels.exists(): - return Response( - {"error": f"Cannot delete logo as it is used by {logo.channels.count()} channel(s)"}, - status=status.HTTP_400_BAD_REQUEST - ) + channel_count = logo.channels.count() + logo.channels.update(logo=None) + logger.info(f"Removed logo {logo.name} from {channel_count} channels before deletion") return super().destroy(request, *args, **kwargs) diff --git a/apps/channels/serializers.py b/apps/channels/serializers.py index a933c496..82b5f808 100644 --- a/apps/channels/serializers.py +++ b/apps/channels/serializers.py @@ -28,6 +28,16 @@ class LogoSerializer(serializers.ModelSerializer): model = Logo fields = ["id", "name", "url", "cache_url", "channel_count", "is_used", "channel_names"] + def validate_url(self, value): + """Validate that the URL is unique for creation or update""" + if self.instance and self.instance.url == value: + return value + + if Logo.objects.filter(url=value).exists(): + raise serializers.ValidationError("A logo with this URL already exists.") + + return value + def get_cache_url(self, obj): # return f"/api/channels/logos/{obj.id}/cache/" request = self.context.get("request") diff --git a/frontend/src/components/forms/Logo.jsx b/frontend/src/components/forms/Logo.jsx index bd711443..c724c21c 100644 --- a/frontend/src/components/forms/Logo.jsx +++ b/frontend/src/components/forms/Logo.jsx @@ -68,6 +68,8 @@ const LogoForm = ({ logo = null, isOpen, onClose }) => { // Handle specific timeout errors if (error.code === 'NETWORK_ERROR' || error.message?.includes('timeout')) { errorMessage = 'Request timed out. Please try again.'; + } else if (error.response?.data?.error) { + errorMessage = error.response.data.error; } notifications.show({ diff --git a/frontend/src/components/tables/LogosTable.jsx b/frontend/src/components/tables/LogosTable.jsx index 41799449..0ec6488f 100644 --- a/frontend/src/components/tables/LogosTable.jsx +++ b/frontend/src/components/tables/LogosTable.jsx @@ -718,6 +718,9 @@ const LogosTable = () => { isBulkDelete ? (
Are you sure you want to delete {selectedRows.size} selected logos? + + Any channels using these logos will have their logo removed. + This action cannot be undone. @@ -727,7 +730,7 @@ const LogosTable = () => { Are you sure you want to delete the logo "{logoToDelete.name}"? {logoToDelete.channel_count > 0 && ( - Warning: This logo is currently used by {logoToDelete.channel_count} channel{logoToDelete.channel_count !== 1 ? 's' : ''}. + This logo is currently used by {logoToDelete.channel_count} channel{logoToDelete.channel_count !== 1 ? 's' : ''}. They will have their logo removed. )}