So I started with Django awhile back using The Definitive Guide To Django which covers Django 1.1. The book says that 1.x versions of Django are backwards compatible, so anything from this book will work in future 1.x releases. Unfortunately, I discovered this week that that isn't 100% true. In Django 1.2.5 FileField was changed so that deleting objects with a FileField doesn't delete the actual file, leaving a bunch of files in your media directory that have no ties to objects in the database.

Some solutions to this that I saw overrode the delete() method on a model to get the path to the file in any FileFields on the model and delete the file. This didn't seem to work consistently for me and I found a couple of comments on blogs stating that the delete process is slightly differently when deleting a QuerySet. What I ended up doing instead, which seems to have worked well, is to tie a method to the post_delete signal of my models with FileFields. The post_delete signal passes an instance of the object which can be used to get the path to the files and delete them and has worked consistently.

The code to do this is nice and simple. I suspect the FieldFile.delete() method can throw an exception if there's an issue with file permissions but it's not documented, so I'm not catching one. I intend to test for it in the next day or two. Take note of the save=False in the delete() method call. By default Django will re-save the instance of that object, leaving you with an excess empty object in the db since it's saving an instance of an object which has already been deleted.

from django.db import models
from django.db.models.signals import post_delete

class BatchFile(models.Model):
    filename = models.FileField(upload_to='my_app/transaction_batches/')
    processed = models.BooleanField(default=False, blank=True)

    def __unicode__(self):
        return u'%s' %self.filename

    class Meta:
        db_table = u'recommend_batch_file'

# Custom signal handlers
def delete_batch_files(sender, **kwargs):
    batch_file = kwargs.get('instance')
    batch_file.filename.delete(save=False)

post_delete.connect(delete_batch_files, sender=BatchFile)