211
Before Django 1.3, the file was deleted from the filesystem automatically when you deleted the corresponding model instance. You are probably using a newer Django version, so you’ll have to implement deleting the file from the filesystem yourself.
Simple signal-based sample
My method of choice at the time of writing is a mix of post_delete
and pre_save
signals, which makes it so that obsolete files are deleted whenever corresponding models are deleted or have their files changed.
Based on a hypothetical MediaFile
model:
import os
import uuid
from django.db import models
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _
class MediaFile(models.Model):
file = models.FileField(_("file"),
upload_to=lambda instance, filename: str(uuid.uuid4()))
# These two auto-delete files from filesystem when they are unneeded:
@receiver(models.signals.post_delete, sender=MediaFile)
def auto_delete_file_on_delete(sender, instance, **kwargs):
"""
Deletes file from filesystem
when corresponding `MediaFile` object is deleted.
"""
if instance.file:
if os.path.isfile(instance.file.path):
os.remove(instance.file.path)
@receiver(models.signals.pre_save, sender=MediaFile)
def auto_delete_file_on_change(sender, instance, **kwargs):
"""
Deletes old file from filesystem
when corresponding `MediaFile` object is updated
with new file.
"""
if not instance.pk:
return False
try:
old_file = MediaFile.objects.get(pk=instance.pk).file
except MediaFile.DoesNotExist:
return False
new_file = instance.file
if not old_file == new_file:
if os.path.isfile(old_file.path):
os.remove(old_file.path)
- I think one of the apps I’ve built a while back used this code in production, but nevertheless use at your own risk.
- For example, there’s a possible data loss scenario: your data might end up referencing a nonexistent file if your
save()
method call happens to be within a transaction that gets rolled back. You could consider wrapping file-removing logic intotransaction.on_commit()
, along the lines oftransaction.on_commit(lambda: os.remove(old_file.path))
, as suggested in Mikhail’s comment.django-cleanup
library does something along those lines. - Edge case: if your app uploads a new file and points model instance to the new file without calling
save()
(e.g. by bulk updating aQuerySet
), the old file will keep lying around because signals won’t be run. This doesn’t happen if you use conventional file handling methods. - Coding style: this example uses
file
as field name, which is not a good style because it clashes with the built-infile
object identifier.
Addendum: periodic cleanup
Realistically, you may want to also run a periodic task to handle orphan file cleanup in case a runtime failure prevents some file from being removed. With that in mind, you could probably get rid of signal handlers altogether, and make such a task the mechanism for dealing with insensitive data and not-so-large files.
Either way though, if you are handling sensitive data, it’s always better to double- or triple- check that you never fail to timely delete data in production to avoid any associated liabilities.
See also
-
FieldFile.delete()
in Django 1.11 model field reference (note that it describes theFieldFile
class, but you’d call.delete()
directly on the field:FileField
instance proxies to the correspondingFieldFile
instance, and you access its methods as if they were field’s)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).
-
Why Django doesn’t delete files automatically: entry in release notes for Django 1.3
In earlier Django versions, when a model instance containing a
FileField
was deleted,FileField
took it upon itself to also delete the file from the backend storage. This opened the door to several data-loss scenarios, including rolled-back transactions and fields on different models referencing the same file. In Django 1.3, when a model is deleted theFileField
’sdelete()
method won’t be called. If you need cleanup of 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).
117
Try django-cleanup, it automatically invokes delete method on FileField when you remove model.
pip install django-cleanup
settings.py
INSTALLED_APPS = (
...
'django_cleanup.apps.CleanupConfig',
)
- [Django]-"gettext()" vs "gettext_lazy()" in Django
- [Django]-How do I convert a Django QuerySet into list of dicts?
- [Django]-Django models: default value for column
60
You can delete file from filesystem with calling .delete
method of file field shown as below with Django >= 1.10:
obj = Song.objects.get(pk=1)
obj.song.delete()
- [Django]-How to write setup.py to include a Git repository as a dependency
- [Django]-How to reset Django admin password?
- [Django]-What is a "slug" in Django?
22
Django 2.x Solution:
It’s very easy to handle file deletion in Django 2. I’ve tried following solution using Django 2 and SFTP Storage and also FTP STORAGE, and I’m pretty sure that it’ll work with any other storage managers which implemented delete
method. (delete
method is one of the storage
abstract methods which is supposed to delete the file from the storage, physically!)
Override the delete
method of the model in a way that the instance deletes its FileFields before deleting itself:
class Song(models.Model):
name = models.CharField(blank=True, max_length=100)
author = models.ForeignKey(User, to_field='id', related_name="id_user2")
song = models.FileField(upload_to='/songs/')
image = models.ImageField(upload_to='/pictures/', blank=True)
date_upload = models.DateField(auto_now_add=True)
def delete(self, using=None, keep_parents=False):
self.song.storage.delete(self.song.name)
self.image.storage.delete(self.image.name)
super().delete()
It works pretty easy for me.
If you want to check if file exists before deletion, you can use storage.exists
. e.g. self.song.storage.exists(self.song.name)
will return a boolean
representing if the song exists. So it will look like this:
def delete(self, using=None, keep_parents=False):
# assuming that you use same storage for all files in this model:
storage = self.song.storage
if storage.exists(self.song.name):
storage.delete(self.song.name)
if storage.exists(self.image.name):
storage.delete(self.image.name)
super().delete()
EDIT (In Addition):
As @HeyMan mentioned, with this solution calling Song.objects.all().delete()
does not delete files! This is happening because Song.objects.all().delete()
is running delete query of Default Manager. So if you want to be able to delete files of a model by using objects
methods, you must write and use a Custom Manager (just for overriding its delete query):
class CustomManager(models.Manager):
def delete(self):
for obj in self.get_queryset():
obj.delete()
and for assigning the CustomManager
to the model, you must initial objects
inside your model:
class Song(models.Model):
name = models.CharField(blank=True, max_length=100)
author = models.ForeignKey(User, to_field='id', related_name="id_user2")
song = models.FileField(upload_to='/songs/')
image = models.ImageField(upload_to='/pictures/', blank=True)
date_upload = models.DateField(auto_now_add=True)
objects = CustomManager() # just add this line of code inside of your model
def delete(self, using=None, keep_parents=False):
self.song.storage.delete(self.song.name)
self.image.storage.delete(self.image.name)
super().delete()
Now you can use .delete()
in the end of any objects
sub-queries. I wrote the simplest CustomManager
, but you can do it better by returning something about objects you deleted or whatever you want.
- [Django]-Django Multiple Authentication Backend for one project
- [Django]-No URL to redirect to. Either provide a url or define a get_absolute_url method on the Model
- [Django]-How to get an ImageField URL within a template?
20
You can also simply overwrite the delete function of the model to check for file if it exists and delete it before calling the super function.
import os
class Excel(models.Model):
upload_file = models.FileField(upload_to='/excels/', blank =True)
uploaded_on = models.DateTimeField(editable=False)
def delete(self,*args,**kwargs):
if os.path.isfile(self.upload_file.path):
os.remove(self.upload_file.path)
super(Excel, self).delete(*args,**kwargs)
- [Django]-Why is factory_boy superior to using the ORM directly in tests?
- [Django]-Retrieving a Foreign Key value with django-rest-framework serializers
- [Django]-POST jQuery array to Django
5
Here is an app that will remove old files whenever model is deleted or a new file is uploaded: django-smartfields
from django.db import models
from smartfields import fields
class Song(models.Model):
song = fields.FileField(upload_to='/songs/')
image = fields.ImageField(upload_to='/pictures/', blank=True)
- [Django]-Django queryset filter – Q() | VS __in
- [Django]-Django/DRF – 405 Method not allowed on DELETE operation
- [Django]-Macros in django templates
5
For those who look for an answer in a newer version of Django (currently 3.1).
I found this website and it worked for me without any changes, just add it in your models.py
:
from django.db.models.signals import post_delete
from django.dispatch import receiver
from django.db import models
""" 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)
""" 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)
- [Django]-How to set environment variables in PyCharm?
- [Django]-Django Multiple Authentication Backend for one project
- [Django]-*_set attributes on Django Models
3
@Anton Strogonoff
I missing something in the code when a file change, if you create a new file generate an error, becuase is a new file a did not find a path. I modified the code of function and added a try/except sentence and it works well.
@receiver(models.signals.pre_save, sender=MediaFile)
def auto_delete_file_on_change(sender, instance, **kwargs):
"""Deletes file from filesystem
when corresponding `MediaFile` object is changed.
"""
if not instance.pk:
return False
try:
old_file = MediaFile.objects.get(pk=instance.pk).file
except MediaFile.DoesNotExist:
return False
new_file = instance.file
if not old_file == new_file:
try:
if os.path.isfile(old_file.path):
os.remove(old_file.path)
except Exception:
return False
- [Django]-How to get an ImageField URL within a template?
- [Django]-What is the difference render() and redirect() in Django?
- [Django]-Set up a scheduled job?
1
I believe it is as simple as deleting the file/image from the object, then deleting the object itself.
template.html
<a href="{% url 'delete_song' song.id %}" value="Delete">Delete</a>
urls.py
path('delete_song/<str:id>', views.delete_song, name='delete_song'),
views.py
def delete_song(request, id):
song_to_delete = Song.objects.get(pk=id)
song_to_delete.image.delete() # delete the file/image
song_to_delete.delete() # finally delete the object
return redirect('home')
- [Django]-Django multiple template inheritance – is this the right style?
- [Django]-Pulling data to the template from an external database with django
- [Django]-You need to install postgresql-server-dev-X.Y for building a server-side extension or libpq-dev for building a client-side application
0
This code will run every time i upload a new image (logo field) and check if a logo already exists if so, close it and remove it from disk. The same procedure could of course be made in receiver function. Hope this helps.
# Returns the file path with a folder named by the company under /media/uploads
def logo_file_path(instance, filename):
company_instance = Company.objects.get(pk=instance.pk)
if company_instance.logo:
logo = company_instance.logo
if logo.file:
if os.path.isfile(logo.path):
logo.file.close()
os.remove(logo.path)
return 'uploads/{0}/{1}'.format(instance.name.lower(), filename)
class Company(models.Model):
name = models.CharField(_("Company"), null=False, blank=False, unique=True, max_length=100)
logo = models.ImageField(upload_to=logo_file_path, default='')
- [Django]-No module named MySQLdb
- [Django]-What is a NoReverseMatch error, and how do I fix it?
- [Django]-Django models: mutual references between two classes and impossibility to use forward declaration in python
0
In django 4 this works well for single objects or queries:
admin.py
class MyFileAdmin(admin.ModelAdmin):
def delete_model(modeladmin, request, queryset):
for obj in queryset:
obj.myFile.delete()
obj.delete()
list_display = ( 'myFile', 'pk')
actions = [delete_model]
- [Django]-Adding to the "constructor" of a django model
- [Django]-Django: Reference to an outer query may only be used in a subquery
- [Django]-How to get getting base_url in django template