Skip to content

Instantly share code, notes, and snippets.

@barseghyanartur
Last active November 24, 2017 18:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save barseghyanartur/f0af5ac2caac9462b7d2 to your computer and use it in GitHub Desktop.
Save barseghyanartur/f0af5ac2caac9462b7d2 to your computer and use it in GitHub Desktop.
Clean media by deleting those files which are no more referenced by any FileField (django command)
# -*- coding: utf-8 -*-
import os
import sys
from six.moves import input
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from django.apps import apps
from django.db import models
class Command(BaseCommand):
""""""
help = 'Clean media by deleting those files which are no more ' \
'referenced by any FileField'
def add_arguments(self, parser):
""""""
parser.add_argument('--noinput',
action = 'store_true',
default = False,
help = 'Clean media by deleting those files which are no more '
'referenced by any FileField without asking for confirmation'
)
def handle(self, *args, **options):
""""""
#print(options)
# Exclude cache folder by default since generally it's used by
# third-party apps
exclude_paths = getattr(settings, 'CLEANUP_EXCLUDE_PATHS', ['cache'])
exclude_roots = [
os.path.abspath(os.path.normpath(os.path.join(settings.MEDIA_ROOT,
path)))
for path in exclude_paths
]
# Build a list of all media files used by our model
models_files = set([])
for model in apps.get_models():
model_objects = list(model.objects.all())
model_file_fields = []
model_files = []
for field_name in model._meta.get_all_field_names():
field = model._meta.get_field(field_name)
if isinstance(field, models.FileField):
model_file_fields.append(str(field_name))
if not len(model_file_fields):
continue
#print(u'')
#print(u'%s %s' % (model.__name__, model_file_fields,))
for obj in model_objects:
for field in model_file_fields:
file = getattr(obj, field, '')
if file:
#print(file.path)
models_files.add(os.path.abspath(file.path))
unref_files = set([])
for root, dirs, files in os.walk(os.path.abspath(settings.MEDIA_ROOT)):
exclude_root = False
for excluded_root in exclude_roots:
if root.find(excluded_root) == 0:
exclude_root = True
break
if exclude_root:
continue
for file in files:
file_path = os.path.abspath(os.path.join(root, file))
if not file_path in models_files:
unref_files.add(file_path)
for file in unref_files:
print('Unreferenced file found: %s' % (file))
num_unref_files = len(unref_files)
print('%s unreferenced file%s found'
'' % (num_unref_files, 's' if num_unref_files else '',))
if num_unref_files:
remove_unref_files = True \
if options['noinput'] \
else (input('Remove all unreferenced files? (y/N) '
'').lower().find('y') == 0)
if remove_unref_files:
for file in unref_files:
try:
os.remove(file)
print('Removed unreferenced file: %s' % (file))
except Exception as err:
print('Failed to remove unreferenced file: '
'%s due to %s' % (file, str(err)))
continue
return
@barseghyanartur
Copy link
Author

Summary of changes (from original):

  • Fixes for Python 3.
  • Improved performance (replaced list with set) for which the in works much faster.
  • Use absolute paths everywhere for cases when media directory is located outside of the Django project.
  • Allow listing of the paths to exclude in CLEANUP_EXCLUDE_PATHS of django settings. Fall back to ['cache'] by default.

@fabiocaccamo
Copy link

fabiocaccamo commented Nov 24, 2017

Thanks for the fixed, by the way this script doesn't work anymore with django >= 1.10:
AttributeError: 'Options' object has no attribute 'get_all_field_names'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment