Skip to main content
The cleanup_orphaned_photos command removes photo files that are no longer referenced in the database, freeing up storage space. This is useful after deleting items or during development when photo uploads fail.

How it works

The command scans photo directories and identifies files that:
  • Are not referenced in the collection_photo table
  • Are from incomplete form submissions (photos uploaded but item never created)
  • Are older than a specified threshold

Basic usage

python manage.py cleanup_orphaned_photos
Always use --dry-run first to preview what will be deleted.

Arguments and options

--dry-run
boolean
Preview mode - show what would be deleted without actually deleting files.Example:
python manage.py cleanup_orphaned_photos --dry-run
--verbose
boolean
Show detailed output including each file being processed.Example:
python manage.py cleanup_orphaned_photos --verbose
--incomplete-only
boolean
Only clean up photos from incomplete form submissions (uploaded but never attached to an item).Example:
python manage.py cleanup_orphaned_photos --incomplete-only
--older-than-hours
integer
default:"24"
Only clean up photos older than specified hours (used with --incomplete-only).Example:
python manage.py cleanup_orphaned_photos --incomplete-only --older-than-hours 48

Cleanup modes

All orphaned photos

Removes all photo files not referenced in the database:
python manage.py cleanup_orphaned_photos --dry-run
python manage.py cleanup_orphaned_photos
What gets deleted:
  • Photos from deleted items
  • Manually deleted files that still exist on disk
  • Any file in item_photos/ or item_photos_avif/ without a database reference

Incomplete submissions only

Removes photos from failed form submissions (safer option):
python manage.py cleanup_orphaned_photos --incomplete-only --older-than-hours 24
What gets deleted:
  • Photos uploaded during item creation but never attached to an item
  • Photo records with content_type_id IS NULL
  • Only photos older than the specified threshold (default: 24 hours)

Example workflows

1

Preview changes

python manage.py cleanup_orphaned_photos --dry-run --verbose
2

Review output

Check the list of files to be deleted and verify they’re safe to remove.
3

Run cleanup

python manage.py cleanup_orphaned_photos

Aggressive cleanup

Remove all incomplete photos older than 1 week:
python manage.py cleanup_orphaned_photos --incomplete-only --older-than-hours 168 --verbose

Production deployment

Run weekly via cron or Celery Beat:
# Crontab entry
0 2 * * 0 cd /var/www/footycollect && venv/bin/python manage.py cleanup_orphaned_photos --incomplete-only

Output example

Starting orphaned photo cleanup...
Found 245 files referenced in database
Found 12 orphaned files

Orphaned files:
  /media/item_photos/temp_abc123.jpg (245678 bytes)
  /media/item_photos_avif/temp_abc123.avif (89234 bytes)
  /media/item_photos/deleted_item_xyz.jpg (512000 bytes)
  ...

Deleted 12 files (1847912 bytes)

Automatic cleanup

FootyCollect includes automatic cleanup via Celery Beat periodic tasks:
  • Incomplete photos - Every 6 hours (photos from failed submissions)
  • All orphaned photos - Every 7 days (comprehensive cleanup)
Configure these tasks with:
python manage.py setup_beat_schedule
See setup_beat_schedule for details.

Storage directories

The command scans these directories:
  • MEDIA_ROOT/item_photos/ - Original uploaded photos
  • MEDIA_ROOT/item_photos_avif/ - AVIF-optimized versions

Database cleanup

When using --incomplete-only, the command also deletes orphaned database records:
DELETE FROM collection_photo 
WHERE uploaded_at < :cutoff_time 
AND content_type_id IS NULL

Troubleshooting

Ensure the user running the command has write permissions on the media directory:
sudo chown -R www-data:www-data /var/www/footycollect/media
Check if files are in use by another process or if the storage backend is remote (S3/R2).For remote storage, files are not automatically deleted from the bucket. Use the cloud provider’s lifecycle policies instead.
If photos are referenced in the database but files are missing, use:
python manage.py shell
from footycollect.collection.models import Photo
Photo.objects.filter(image__isnull=False).count()
Then investigate missing files in logs.