using FileField.delete_file to remove unused images

456 views
Skip to first unread message

ER Yost

unread,
Dec 11, 2007, 12:54:39 PM12/11/07
to Django users
I am working on a project with the following:

class Person(models.Model):
# ...
picture = models.ImageField(...)

I'd like to give each Person the ability to delete images, and I'd
like to remove deleted pictures from the filesystem once a person has
deleted them. From what I can tell, the delete_file function in the
FileField class (http://code.djangoproject.com/browser/django/trunk/
django/db/models/fields/__init__.py, line 767) will do that for me.
However, I haven't gotten it to work. Here's a chunk of the code I'm
working with...

class PersonForm(forms.Form):
# ...
picture = forms.ImageField(required=False)
# ...
def save(self):
# ...
p = Person.objects.get(pk=pk)
file, content = (self.cleaned_data['picture'].filename,
self.cleaned_data['picture'].content)

# I'm most confused on how to get the instance of ImageField
class to operate on
# this is the best thing I could come up with
picfield = [f for f in p.__class__._meta.fields if f.name ==
'picture'][0]
picfield.delete_file(p)
p.save_picture_file(file, content)
p.save()
# return None

Django doesn't argue with the syntax, but it also won't delete the
file from the filesystem. My main assumptions are that:
delete_file accepts the instance of the model containing the
ImageField (in this example, Person)
I have the steps (get field, delete file, save new file, save
person instance) in the right order

l5x

unread,
Dec 11, 2007, 1:05:10 PM12/11/07
to Django users
I didn't use delete_file, but if you can't solve this you can write
your own code for that using signals (pre_delete or post_delete).
More: http://code.djangoproject.com/wiki/Signals

Anyway I'm also curious how to do that without signals.

Marty Alchin

unread,
Dec 11, 2007, 2:11:44 PM12/11/07
to django...@googlegroups.com
There are several things you need to know, so I'll lay them out.

1) You can get any Field instance from a class by using the
model._meta.get_field() method. Instead of your list comprehension,
just use the following line:

picfield = p._meta.get_field('picture')

2) If you read the comments in delete_file(), you'll see the
following: "If the file exists and no other object of this type
references it, delete it from the filesystem." Your problem is that
the Person "p" still references the file, so delete_file() won't
delete it. You'd need to put something like this before the
delete_file() call in order to get it to work properly:

p.picture = ''
p.save()

3) Saving an object with a new filename alters the corresponding field
in the database record. What this means is that you can somewhat
ignore the previous item, and simply put delete_file() at the *end* of
your script, instead of before the file saving code. That way, by the
time it runs, the Person "p" will already have a different filename,
and delete_file() won't find it, and will happily delete the file for
you.

4) save_FOO_file() automatically saves the model instance with the new
filename. Right now, you're calling p.save() after
p.save_picture_file(), which results in two hits to the database for
absolutely no reason. save_picture_file() calls save() internally, so
don't bother doing it again.

5) With those changes, your code should look something like this:

class PersonForm(forms.Form):
# ...
picture = forms.ImageField(required=False)
# ...
def save(self):
# ...
p = Person.objects.get(pk=pk)
file, content = (self.cleaned_data['picture'].filename,
self.cleaned_data['picture'].content)

p.save_picture_file(file, content)
p._meta.get_field('picture').delete_file(p)

6) Deleting files will get much easier soon, and it will even be a bit
simpler to do what you're looking to do, without having to build all
this into your form logic. So most of the things I've just covered
will change, but for the better.

-Gul

Reply all
Reply to author
Forward
0 new messages