In Django, when you delete a model instance from the database the files uploaded to those instances are not deleted automatically. This can lead to having lots of “orphaned” files: they’re not referenced by the database, but they’re still taking up space on the server.
In this post, I’ll share how to change that so those files get automatically deleted whenever their associated model instances are deleted.
First, to clarify the problem, Django’s documentation says:
Note that when a model is deleted, related files are not deleted. If you need to cleanup orphaned files, you’ll need to handle it yourself (for instance, with a custom management command that can be run manually or scheduled to run periodically via e.g. cron).
https://docs.djangoproject.com/en/3.0/ref/models/fields/#django.db.models.fields.files.FieldFile.delete
I think they don’t handle cleaning up the uploaded files because Django doesn’t know for certain if you’re using those files somewhere else, or if the deletion occurred during a database transaction and might be rolled back. So sometimes it’s not so cut-and-dry.
But if you aren’t using database transactions, and know for certain that don’t need those files once their model instance got deleted, here’s a code snippet to take care of that:
from django.db.models.signals import post_delete
from django.dispatch import receiver
from django.db import models
""" Whenever ANY model is deleted, if it has a file field on it, delete the associated file too"""
@receiver(post_delete)
def delete_files_when_row_deleted_from_db(sender, instance, **kwargs):
for field in sender._meta.concrete_fields:
if isinstance(field,models.FileField):
instance_file_field = getattr(instance,field.name)
delete_file_if_unused(sender,instance,field,instance_file_field)
""" Delete the file if something else get uploaded in its place"""
@receiver(pre_save)
def delete_files_when_file_changed(sender,instance, **kwargs):
# Don't run on initial save
if not instance.pk:
return
for field in sender._meta.concrete_fields:
if isinstance(field,models.FileField):
#its got a file field. Let's see if it changed
try:
instance_in_db = sender.objects.get(pk=instance.pk)
except sender.DoesNotExist:
# We are probably in a transaction and the PK is just temporary
# Don't worry about deleting attachments if they aren't actually saved yet.
return
instance_in_db_file_field = getattr(instance_in_db,field.name)
instance_file_field = getattr(instance,field.name)
if instance_in_db_file_field.name != instance_file_field.name:
delete_file_if_unused(sender,instance,field,instance_in_db_file_field)
""" Only delete the file if no other instances of that model are using it"""
def delete_file_if_unused(model,instance,field,instance_file_field):
dynamic_field = {}
dynamic_field[field.name] = instance_file_field.name
other_refs_exist = model.objects.filter(**dynamic_field).exclude(pk=instance.pk).exists()
if not other_refs_exist:
instance_file_field.delete(False)
Just copy-and-paste that into a module’s models.py, and it will take care of deleting the files on all of your models whenever the model instance is deleted, or when something new is uploaded in place of the old file. It also has a safe-guard that double-checks no other instances of that same model have a reference to that file before deleting it (although it doesn’t check other models or other fields.)
This code snippet is based off Matthias Omisore’s.
Questions or comments welcome!