Coverage for apps / recipes / management / commands / cleanup_search_images.py: 0%
41 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-14 19:13 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-14 19:13 +0000
1"""
2Management command to clean up old unused cached search images.
4Deletes CachedSearchImage records and files that haven't been accessed
5in the specified number of days. Actively used images (displayed in search
6or reused during recipe import) are preserved via last_accessed_at updates.
8Usage:
9 python manage.py cleanup_search_images --days=30
10 python manage.py cleanup_search_images --days=30 --dry-run
11"""
13import logging
14from datetime import timedelta
16from django.core.management.base import BaseCommand
17from django.utils import timezone
19from apps.recipes.models import CachedSearchImage
21logger = logging.getLogger(__name__)
24class Command(BaseCommand):
25 help = "Delete cached search images older than specified days (default: 30)"
27 def add_arguments(self, parser):
28 parser.add_argument(
29 "--days",
30 type=int,
31 default=30,
32 help="Delete images not accessed in this many days (default: 30)",
33 )
34 parser.add_argument(
35 "--dry-run",
36 action="store_true",
37 help="Show what would be deleted without actually deleting",
38 )
40 def handle(self, *args, **options):
41 days = options["days"]
42 dry_run = options["dry_run"]
44 # Calculate cutoff date
45 cutoff_date = timezone.now() - timedelta(days=days)
47 # Find old cached images
48 old_images = CachedSearchImage.objects.filter(last_accessed_at__lt=cutoff_date)
50 count = old_images.count()
52 if count == 0:
53 self.stdout.write(self.style.SUCCESS(f"No cached images older than {days} days found."))
54 return
56 # Show what will be deleted
57 self.stdout.write(
58 self.style.WARNING(
59 f"Found {count} cached image(s) not accessed since {cutoff_date.strftime('%Y-%m-%d %H:%M:%S')}"
60 )
61 )
63 if dry_run:
64 self.stdout.write(self.style.NOTICE("\n[DRY RUN] Would delete the following images:"))
65 for img in old_images[:10]: # Show first 10
66 self.stdout.write(
67 f" - ID {img.id}: {img.external_url[:80]}... "
68 f"(last accessed: {img.last_accessed_at.strftime('%Y-%m-%d')})"
69 )
70 if count > 10:
71 self.stdout.write(f" ... and {count - 10} more")
73 self.stdout.write(
74 self.style.NOTICE(f"\n[DRY RUN] Run without --dry-run to actually delete {count} image(s)")
75 )
76 return
78 # Actually delete the images
79 deleted_count = 0
80 for img in old_images:
81 try:
82 # Delete the file from disk if it exists
83 if img.image:
84 img.image.delete(save=False)
86 # Delete the database record
87 img.delete()
88 deleted_count += 1
89 except Exception as e:
90 logger.error(f"Failed to delete cached image {img.id}: {e}")
92 self.stdout.write(
93 self.style.SUCCESS(f"Successfully deleted {deleted_count} cached image(s) older than {days} days.")
94 )
96 if deleted_count < count:
97 self.stdout.write(
98 self.style.WARNING(
99 f"Warning: {count - deleted_count} image(s) failed to delete. Check logs for details."
100 )
101 )