Replacing files in a Django ImageField or FieldField
Sep 24, 2008

Django provides a lot of really useful tools to simplify the development process and let you focus only on the important bits. The FileField and ImageField (a subclass of FileField) are good examples of that letting you simply tell Django that your model will have a file or image and letting it take care of the issues of uploading, storing, and all that. In the past, that's really been enough. It will even automatically delete the file/image when you delete its parent model object. One thing it doesn't do, however (and this has been the topic of much debate), is delete a previously uploaded file/image when you upload a new one for the same model. What I mean by this is if you have a model with a FileField defined and use it to upload some file associated with an object. If you then later decide that you want a different file associated with that object and upload it, it doesn't delete the original file and instead leaves it in place and only changes the path stored in the database to point to the new file. Depending on the nature and traffic your website gets, this can lead to massive amounts of storage being wasted on orphaned files (assuming you don't want to keep those old files, of course). I toyed with a couple different approaches to this, including the possibility of subclassing the FileField to try and add that functionalty directly to the field. While this would probably work, I instead opted for a less generalized method: overriding the save() method of the model to take care of this: def save(self, force_insert=False, force_update=False): try: old_obj = ModelName.objects.get(pk=self.pk) if old_obj.image.path != self.image.path: path = old_obj.image.path default_storage.delete(path) except: pass super(ModelName, self).save(force_insert, force_update) This work perfectly, though it has the disadvantage of being specific to a particular model, which violates the DRY principle (assuming you're going to use it on more than one model). Fortunately there's a simple way to solve this problem too: subclassing models.Model and then instead of having all your models subclass models.Model directly, have them subclass your own version of it instead. In that case you'll probably want to have it work generically on all ImageFields and/or FileFields rather than having to name them all specifically. This isn't too hard, and you can build up a list of all the ImageFields for a particular model like this (taken from the sorl-thumnail project): for field in model._meta.fields: if isinstance(field, models.ImageField): if field.upload_to.find("%") == -1: paths = paths.union((field.upload_to,)) [Edit: There was a bug in my code that I've corrected. Details are below in the comment by Comete.] [Edit2: Fixed another problem in the code with variable names. Thanks, again Comete!]
blog comments powered by Disqus