Dynamic upload path for Django FileField/ImageField
November 18th, 2008
Django provides an extraordinarily easy way of integrating file uploads into your site in the FileField (also the ImageField which provides some nifty image-specific functionality). However as soon as you start dealing with file uploads you have to worry about where those files are going to be stored. To take care of this, the 'upload_to' argument is set when declaring your FileField which is a local filesystem path that will be appended to your MEDIA_ROOT to the upload location (you could also use an absolute path). By default this is just a static string, though it does handle strftime arguments to allow some flexibility and will look something like this:
def model(models.Model):
file = models.FileField(upload_to='my/file/uploads/')
...
However that's not always going to cut it. Sometimes you're going to want more flexibility that even just separating the files out by date information. In that case you can take advantage of the fact that a callable can be passed as an argument. In one of my projects I've defined a function that returns the appropriate path depending on a couple of different variables. In this particular case, I know that all uploads for a certain model will be PDFs, while all uploads for certain other models will be images, so I'm able to pretty easily generate appropriate paths for those uploads. We also wanted to obfuscate the filenames so that someone couldn't easily guess the path for other files, and didn't really like the way that Django simply keeps appending '_'s to filenames in the event of a filename collision until it ends up with a unique name. To handle that I simply generate a random string to use as the filename, which fits our needs quite nicely. In my specific case, the function lives in myproject.lib.files and looks like this:
import random, string
from django.contrib.contenttypes.models import ContentType
def get_path(instance, filename):
ctype = ContentType.objects.get_for_model(instance)
model = ctype.model
app = ctype.app_label
extension = filename.split('.')[-1]
dir = "site"
if model == "job":
dir += "/pdf/job_attachment"
else:
dir += "/img/%s" % app
if model == "image_type_1":
dir += "/type1/%s" % instance.category
elif model == "image_type_2":
dir += "/type2"
elif model == "restaurant":
dir += "/logo"
else:
dir += "/%s" % model
chars = string.letters + string.digits
name = string.join(random.sample(chars, 8), '')
return "%s/%s.%s" % (dir, name, extension)
As you can see, it's pretty simple to generate an upload path based on pretty much any characteristics you want. To then tell Django to use this function to get the upload path rather than a static string you modify your model definition like so:
from myproject.lib.files import get_path
def model(models.Model):
file = models.FileField(upload_to=get_path)
...
And that's all it takes. Now all my uploads are automatically renamed and uploaded into the particular directory structure that we want. And since all the logic involved is in a single function it's extremely easy to change it at any point if we decide to alter our directory structure.