Coverage for apps / recipes / management / commands / cleanup_search_images.py: 0%
41 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 00:40 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 00:40 +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(
49 last_accessed_at__lt=cutoff_date
50 )
52 count = old_images.count()
54 if count == 0:
55 self.stdout.write(
56 self.style.SUCCESS(
57 f'No cached images older than {days} days found.'
58 )
59 )
60 return
62 # Show what will be deleted
63 self.stdout.write(
64 self.style.WARNING(
65 f'Found {count} cached image(s) not accessed since {cutoff_date.strftime("%Y-%m-%d %H:%M:%S")}'
66 )
67 )
69 if dry_run:
70 self.stdout.write(
71 self.style.NOTICE('\n[DRY RUN] Would delete the following images:')
72 )
73 for img in old_images[:10]: # Show first 10
74 self.stdout.write(
75 f' - ID {img.id}: {img.external_url[:80]}... '
76 f'(last accessed: {img.last_accessed_at.strftime("%Y-%m-%d")})'
77 )
78 if count > 10:
79 self.stdout.write(f' ... and {count - 10} more')
81 self.stdout.write(
82 self.style.NOTICE(
83 f'\n[DRY RUN] Run without --dry-run to actually delete {count} image(s)'
84 )
85 )
86 return
88 # Actually delete the images
89 deleted_count = 0
90 for img in old_images:
91 try:
92 # Delete the file from disk if it exists
93 if img.image:
94 img.image.delete(save=False)
96 # Delete the database record
97 img.delete()
98 deleted_count += 1
99 except Exception as e:
100 logger.error(f'Failed to delete cached image {img.id}: {e}')
102 self.stdout.write(
103 self.style.SUCCESS(
104 f'Successfully deleted {deleted_count} cached image(s) older than {days} days.'
105 )
106 )
108 if deleted_count < count:
109 self.stdout.write(
110 self.style.WARNING(
111 f'Warning: {count - deleted_count} image(s) failed to delete. Check logs for details.'
112 )
113 )