Dynamic upload paths in Django
For a while I’ve been using the CustomImageField as a way to specify an upload path for images. It’s a hack that lets you use ids or slugs from your models in the upload path, e.g.:
/path/to/media/photos/1234/flowers.jpg
or
/path/to/media/photos/scotland-trip/castle.jpg
CustomImageField no more
Since the FileStorageRefactor was merged in to trunk r8244, it’s no longer necessary to use the custom field. Other recent changes to trunk mean that it doesn’t work any more in its current state, so this is a good time to retire it.
Pass a callable in upload_to
It is now possible for the upload_to
parameter of the FileField
or ImageField
to be a callable, instead of a string. The callable is passed the current model instance and uploaded file name and must return a path. That sounds ideal.
Here’s an example:
import os from django.db import models def get_image_path(instance, filename): return os.path.join('photos', str(instance.id), filename) class Photo(models.Model): image = models.ImageField(upload_to=get_image_path)
get_image_path
is the callable (in this case a function). It simply gets the id from the instance of Photo
and uses that in the upload path. Images will be uploaded to paths like:
photos/1/kitty.jpg
You can use whatever fields are in the instance (slugs, etc), or fields in related models. For example, if Photo
models are associated with an Album
model, the upload path for a Photo
could include the Album
slug.
Note that if you are using the id, you need to make sure the model instance was saved before you upload the file. Otherwise, the id hasn’t been set at that point and can’t be used.
For reference, here’s what the main part of the view might look like:
... if request.method == 'POST': form = PhotoForm(request.POST, request.FILES) if form.is_valid(): photo = Photo.objects.create() image_file = request.FILES['image'] photo.image.save(image_file.name, image_file) ...
This is much simpler than the hacks used in CustomImageField
and provides a nice flexible way to specify file or image upload paths per-model instance.
Note: If you are using ModelForm, when you call form.save()
it will save the file – no need to do it yourself as in the example above.
Update: Using the id of the instance doesn’t work any more because it’s probably not set when your function is called. Try using a field such as a slug instead, or the id of a parent object (e.g. if the Photo
is in an Album
, use the Album
‘s id.