Skip to content
Snippets Groups Projects
Select Git revision
  • 14b3fd00f9e86fe98c314a33fdb9688d4894812d
  • main default
2 results

index.js

Blame
  • clean_media.py 3.12 KiB
    import os
    
    from django.apps import apps
    from django.db.models import Q
    from django.db.models import FileField
    from django.conf import settings
    from django.core.management import BaseCommand
    
    
    class Command(BaseCommand):
        """Management command to delete all unused media files.
    
        Inspired by
        -----------
        https://www.algotech.solutions/blog/python/deleting-unused-django-media-files/
        """
    
        help = "Delete all unused media files."
        media_root = getattr(settings, 'MEDIA_ROOT', None)
    
        def get_db_files(self):
            """Retrieve all references to files in the database.
    
            Returns
            -------
            db_files : set
            """
            all_models = apps.get_models()
            db_files = set()
    
            for model in all_models:
                file_fields = []
                filters = Q()
                for field in model._meta.fields:
                    if isinstance(field, FileField):
                        file_fields.append(field.name)
                        is_null = {'{}__isnull'.format(field.name): True}
                        is_empty = {'{}__exact'.format(field.name): ''}
                        filters &= Q(**is_null) | Q(**is_empty)
                # only retrieve the models which have non-empty, non-null file
                # fields
                if file_fields:
                    files = (model.objects.exclude(filters)
                             .values_list(*file_fields, flat=True)
                             .distinct())
                    db_files.update(files)
    
            return db_files
    
        def get_physical_files(self):
            """Retrieve the set of physical media files.
    
            Returns
            -------
            physical_files : set
            """
            physical_files = set()
    
            if not self.media_root:
                return physical_files
    
            # Get all files from the MEDIA_ROOT, recursively
            for relative_root, dirs, files in os.walk(self.media_root):
                for file_ in files:
                    physical_files.add(file_)
    
            return physical_files
    
        def handle(self, *args, **options):
            """Find unused media files and delete them."""
            db_files = self.get_db_files()
            physical_files = self.get_physical_files()
    
            # delete physical files that have no references in the DB
            deletables = physical_files - db_files
    
            if deletables:
                unused = len(deletables)
                self.stdout.write('Unused media files were detected:')
                self.stdout.write('\n'.join(f_ for f_ in deletables))
    
                for file_ in deletables:
                    os.remove(os.path.join(self.media_root, file_))
    
                # Delete all empty folders, bottom-up
                walk = os.walk(self.media_root, topdown=False)
                for relative_root, dirs, files in walk:
                    for dir_ in dirs:
                        if not os.listdir(os.path.join(relative_root, dir_)):
                            os.rmdir(os.path.join(relative_root, dir_))
                self.stdout.write(
                    self.style.SUCCESS('Removed {} unused media file(s).'
                                       .format(unused)))
            else:
                self.stdout.write(self.style.SUCCESS(
                    'No unused media files detected.'))