Ask to delete local files as well.

This commit is contained in:
SergeantPanda 2025-07-18 15:23:30 -05:00
parent d926d90dd9
commit bc08cb1270
4 changed files with 93 additions and 15 deletions

View file

@ -1052,12 +1052,28 @@ class BulkDeleteLogosAPIView(APIView):
)
def delete(self, request):
logo_ids = request.data.get("logo_ids", [])
delete_files = request.data.get("delete_files", False)
# Get logos and their usage info before deletion
logos_to_delete = Logo.objects.filter(id__in=logo_ids)
total_channels_affected = 0
local_files_deleted = 0
for logo in logos_to_delete:
# Handle file deletion for local files
if delete_files and logo.url and logo.url.startswith('/data/logos'):
try:
if os.path.exists(logo.url):
os.remove(logo.url)
local_files_deleted += 1
logger.info(f"Deleted local logo file: {logo.url}")
except Exception as e:
logger.error(f"Failed to delete logo file {logo.url}: {str(e)}")
return Response(
{"error": f"Failed to delete logo file {logo.url}: {str(e)}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
if logo.channels.exists():
channel_count = logo.channels.count()
total_channels_affected += channel_count
@ -1071,6 +1087,8 @@ class BulkDeleteLogosAPIView(APIView):
message = f"Successfully deleted {deleted_count} logos"
if total_channels_affected > 0:
message += f" and removed them from {total_channels_affected} channels"
if local_files_deleted > 0:
message += f" and deleted {local_files_deleted} local files"
return Response(
{"message": message},
@ -1157,6 +1175,20 @@ class LogoViewSet(viewsets.ModelViewSet):
def destroy(self, request, *args, **kwargs):
"""Delete a logo and remove it from any channels using it"""
logo = self.get_object()
delete_file = request.query_params.get('delete_file', 'false').lower() == 'true'
# Check if it's a local file that should be deleted
if delete_file and logo.url and logo.url.startswith('/data/logos'):
try:
if os.path.exists(logo.url):
os.remove(logo.url)
logger.info(f"Deleted local logo file: {logo.url}")
except Exception as e:
logger.error(f"Failed to delete logo file {logo.url}: {str(e)}")
return Response(
{"error": f"Failed to delete logo file: {str(e)}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
# Instead of preventing deletion, remove the logo from channels
if logo.channels.exists():

View file

@ -1337,9 +1337,15 @@ export default class API {
}
}
static async deleteLogo(id) {
static async deleteLogo(id, deleteFile = false) {
try {
await request(`${host}/api/channels/logos/${id}/`, {
const params = new URLSearchParams();
if (deleteFile) {
params.append('delete_file', 'true');
}
const url = `${host}/api/channels/logos/${id}/?${params.toString()}`;
await request(url, {
method: 'DELETE',
});
@ -1351,11 +1357,16 @@ export default class API {
}
}
static async deleteLogos(ids) {
static async deleteLogos(ids, deleteFiles = false) {
try {
const body = { logo_ids: ids };
if (deleteFiles) {
body.delete_files = true;
}
await request(`${host}/api/channels/logos/bulk-delete/`, {
method: 'DELETE',
body: { logo_ids: ids },
body: body,
});
// Remove multiple logos from store

View file

@ -29,12 +29,15 @@ const ConfirmationDialog = ({
onSuppressChange,
size = 'md',
zIndex = 1000,
showDeleteFileOption = false,
deleteFileLabel = "Also delete files from disk",
}) => {
const suppressWarning = useWarningsStore((s) => s.suppressWarning);
const isWarningSuppressed = useWarningsStore((s) => s.isWarningSuppressed);
const [suppressChecked, setSuppressChecked] = useState(
isWarningSuppressed(actionKey)
);
const [deleteFiles, setDeleteFiles] = useState(false);
const handleToggleSuppress = (e) => {
setSuppressChecked(e.currentTarget.checked);
@ -47,13 +50,23 @@ const ConfirmationDialog = ({
if (suppressChecked) {
suppressWarning(actionKey);
}
onConfirm();
if (showDeleteFileOption) {
onConfirm(deleteFiles);
} else {
onConfirm();
}
setDeleteFiles(false); // Reset for next time
};
const handleClose = () => {
setDeleteFiles(false); // Reset for next time
onClose();
};
return (
<Modal
opened={opened}
onClose={onClose}
onClose={handleClose}
title={title}
size={size}
centered
@ -70,8 +83,17 @@ const ConfirmationDialog = ({
/>
)}
{showDeleteFileOption && (
<Checkbox
checked={deleteFiles}
onChange={(event) => setDeleteFiles(event.currentTarget.checked)}
label={deleteFileLabel}
mb="md"
/>
)}
<Group justify="flex-end">
<Button variant="outline" onClick={onClose}>
<Button variant="outline" onClick={handleClose}>
{cancelLabel}
</Button>
<Button color="red" onClick={handleConfirm}>

View file

@ -156,10 +156,10 @@ const LogosTable = () => {
/**
* Functions
*/
const executeDeleteLogo = useCallback(async (id) => {
const executeDeleteLogo = useCallback(async (id, deleteFile = false) => {
setIsLoading(true);
try {
await API.deleteLogo(id);
await API.deleteLogo(id, deleteFile);
await fetchLogos();
notifications.show({
title: 'Success',
@ -182,12 +182,12 @@ const LogosTable = () => {
}
}, [fetchLogos]);
const executeBulkDelete = useCallback(async () => {
const executeBulkDelete = useCallback(async (deleteFiles = false) => {
if (selectedRows.size === 0) return;
setIsLoading(true);
try {
await API.deleteLogos(Array.from(selectedRows));
await API.deleteLogos(Array.from(selectedRows), deleteFiles);
await fetchLogos();
notifications.show({
@ -706,11 +706,11 @@ const LogosTable = () => {
<ConfirmationDialog
opened={confirmDeleteOpen}
onClose={() => setConfirmDeleteOpen(false)}
onConfirm={() => {
onConfirm={(deleteFiles) => {
if (isBulkDelete) {
executeBulkDelete();
executeBulkDelete(deleteFiles);
} else {
executeDeleteLogo(deleteTarget);
executeDeleteLogo(deleteTarget, deleteFiles);
}
}}
title={isBulkDelete ? "Delete Multiple Logos" : "Delete Logo"}
@ -744,6 +744,19 @@ const LogosTable = () => {
confirmLabel="Delete"
cancelLabel="Cancel"
size="md"
showDeleteFileOption={
isBulkDelete
? Array.from(selectedRows).some(id => {
const logo = Object.values(logos).find(l => l.id === id);
return logo && logo.url && logo.url.startsWith('/data/logos');
})
: logoToDelete && logoToDelete.url && logoToDelete.url.startsWith('/data/logos')
}
deleteFileLabel={
isBulkDelete
? "Also delete local logo files from disk"
: "Also delete logo file from disk"
}
/>
<ConfirmationDialog