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

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(last_accessed_at__lt=cutoff_date) 

49 

50 count = old_images.count() 

51 

52 if count == 0: 

53 self.stdout.write(self.style.SUCCESS(f"No cached images older than {days} days found.")) 

54 return 

55 

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 ) 

62 

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

72 

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 

77 

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) 

85 

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

91 

92 self.stdout.write( 

93 self.style.SUCCESS(f"Successfully deleted {deleted_count} cached image(s) older than {days} days.") 

94 ) 

95 

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 ) 

← Back to Dashboard