import os from django.conf import settings from django.core.management.base import BaseCommand from boxes.models import ThingFile class Command(BaseCommand): help = 'Clean up orphaned files from deleted things' def add_arguments(self, parser): parser.add_argument( '--dry-run', action='store_true', dest='dry_run', help='Show what would be deleted without actually deleting', ) def handle(self, *args, **options): dry_run = options.get('dry_run', False) if dry_run: self.stdout.write(self.style.WARNING('DRY RUN - No files will be deleted')) self.stdout.write('Finding orphaned files...') media_root = settings.MEDIA_ROOT things_files_root = os.path.join(media_root, 'things', 'files') if not os.path.exists(things_files_root): self.stdout.write(self.style.WARNING('No things/files directory found')) return valid_paths = set() for thing_file in ThingFile.objects.all(): if thing_file.file: file_path = thing_file.file.path if os.path.exists(file_path): valid_paths.add(os.path.relpath(file_path, things_files_root)) self.stdout.write(f'Found {len(valid_paths)} valid files in database') deleted_count = 0 empty_dirs_removed = 0 for root, dirs, files in os.walk(things_files_root, topdown=False): for filename in files: file_path = os.path.join(root, filename) relative_path = os.path.relpath(file_path, things_files_root) if relative_path not in valid_paths: deleted_count += 1 if dry_run: self.stdout.write(f'Would delete: {file_path}') else: try: os.remove(file_path) self.stdout.write(f'Deleted: {file_path}') except OSError as e: self.stdout.write(self.style.ERROR(f'Failed to delete {file_path}: {e}')) for dirname in dirs: dir_path = os.path.join(root, dirname) if not os.listdir(dir_path): if dry_run: self.stdout.write(f'Would remove empty directory: {dir_path}') else: try: os.rmdir(dir_path) self.stdout.write(f'Removed empty directory: {dir_path}') empty_dirs_removed += 1 except OSError as e: self.stdout.write(self.style.ERROR(f'Failed to remove {dir_path}: {e}')) if dry_run: self.stdout.write(self.style.WARNING(f'\nDry run complete. Would delete {deleted_count} files')) self.stdout.write(f'Would remove {empty_dirs_removed} empty directories') else: self.stdout.write(self.style.SUCCESS(f'\nCleanup complete! Deleted {deleted_count} orphaned files')) self.stdout.write(f'Removed {empty_dirs_removed} empty directories')