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

1""" 

2Management command to clean up old unused cached search images. 

3 

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. 

7 

8Usage: 

9 python manage.py cleanup_search_images --days=30 

10 python manage.py cleanup_search_images --days=30 --dry-run 

11""" 

12 

13import logging 

14from datetime import timedelta 

15 

16from django.core.management.base import BaseCommand 

17from django.utils import timezone 

18 

19from apps.recipes.models import CachedSearchImage 

20 

21logger = logging.getLogger(__name__) 

22 

23 

24class Command(BaseCommand): 

25 help = 'Delete cached search images older than specified days (default: 30)' 

26 

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 ) 

39 

40 def handle(self, *args, **options): 

41 days = options['days'] 

42 dry_run = options['dry_run'] 

43 

44 # Calculate cutoff date 

45 cutoff_date = timezone.now() - timedelta(days=days) 

46 

47 # Find old cached images 

48 old_images = CachedSearchImage.objects.filter( 

49 last_accessed_at__lt=cutoff_date 

50 ) 

51 

52 count = old_images.count() 

53 

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 

61 

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 ) 

68 

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') 

80 

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 

87 

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) 

95 

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}') 

101 

102 self.stdout.write( 

103 self.style.SUCCESS( 

104 f'Successfully deleted {deleted_count} cached image(s) older than {days} days.' 

105 ) 

106 ) 

107 

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 ) 

← Back to Dashboard