This commit is contained in:
nvrl committed 2026-04-27 03:36:35 +02:00
1 parent 2b2b4cf48f
commit 22fe0674dd
22 files changed
+620 -286

No files matched your search

@@ -0,0 +1,94 @@
"""Garbage collect orphan flavor image files.
Walks MEDIA_ROOT/flavors/ and removes directories whose paths are not referenced
by any Flavor.local_image_paths or Flavor.main_image_path. Use --dry-run to
preview without deleting.
Files referenced by the legacy Flavor.image ImageField are also preserved (for
backwards compatibility) until that field is dropped.
"""
from __future__ import annotations
import os
import shutil
from pathlib import Path
from django.conf import settings
from django.core.management.base import BaseCommand
from api.models import Flavor
class Command(BaseCommand):
help = "Remove orphan flavor image files / directories not referenced by any Flavor."
def add_arguments(self, parser):
parser.add_argument(
"--dry-run",
action="store_true",
help="Print what would be deleted but don't touch the filesystem.",
)
def handle(self, *args, **options):
dry_run: bool = options["dry_run"]
flavors_root = Path(settings.MEDIA_ROOT) / "flavors"
if not flavors_root.exists():
self.stdout.write(self.style.WARNING(f"No flavors dir at {flavors_root}"))
return
# Collect every relative path/directory that should be kept.
referenced_files: set[str] = set()
referenced_dirs: set[str] = set()
legacy_image_files: set[str] = set()
for flavor in Flavor.objects.all().only(
"local_image_paths", "main_image_path", "image"
):
paths = list(flavor.local_image_paths or [])
if flavor.main_image_path:
paths.append(flavor.main_image_path)
for rel in paths:
referenced_files.add(rel)
referenced_dirs.add(os.path.dirname(rel))
if flavor.image:
legacy_image_files.add(flavor.image.name)
# Walk the flavors tree and decide what's unreferenced.
deleted_dirs: list[str] = []
deleted_files: list[str] = []
kept = 0
for entry in sorted(flavors_root.iterdir()):
rel_dir = f"flavors/{entry.name}"
if entry.is_dir():
if rel_dir in referenced_dirs:
# Drop only orphan files inside the referenced dir.
for f in entry.iterdir():
rel_file = f"{rel_dir}/{f.name}"
if f.is_file() and rel_file not in referenced_files:
deleted_files.append(rel_file)
if not dry_run:
f.unlink()
else:
kept += 1
else:
deleted_dirs.append(rel_dir)
if not dry_run:
shutil.rmtree(entry)
elif entry.is_file():
rel_file = f"flavors/{entry.name}"
if rel_file in legacy_image_files or rel_file in referenced_files:
kept += 1
else:
deleted_files.append(rel_file)
if not dry_run:
entry.unlink()
verb = "Would delete" if dry_run else "Deleted"
self.stdout.write(self.style.SUCCESS(f"{verb}: {len(deleted_dirs)} dirs, {len(deleted_files)} files"))
for path in deleted_dirs:
self.stdout.write(f" dir {path}")
for path in deleted_files:
self.stdout.write(f" file {path}")
self.stdout.write(f"Kept: {kept} entries")