Josh Ourisman » On the other hand

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.

Stuart Marsh wrote:

on Tuesday, November 18th 2008 at 3:31 p.m.

Hi Josh,

Thanks for the article. It's not exactly what I'm after but it's given me some ideas.

In my case I am uploading pictures for a user profile. I want the pictures stored in a directory called username. Currently I override the save function in the model, and create a new folder with the users name. Then I copy across the file that's just been uploaded and update the imagefield data. Not very scalable, but it works.

I'm just wondering if this could be done in the way you've described.

Stuart

Josh wrote:

on Tuesday, November 18th 2008 at 4:14 p.m.

Stuart,

In that case I think something like this should work for you:

def get_path(instance, filename):
    dir = "profile_pictures/%s/%s" % (instance.username, filename)

mechanix wrote:

on Wednesday, November 19th 2008 at 5:43 a.m.

Nice article, thanks!

A side note: The line with

dir = "site"

has bad indent (0 spaces instead of 4)

Josh wrote:

on Wednesday, November 19th 2008 at 1:29 p.m.

Yeah, not too sure what's up with that. It's got the proper number of spaces in the db... I've tried a couple different methods of getting it indented, but not of them seem to actually do anything. :/

I'm using Pygments for the syntax highlighting and such, so perhaps it's a problem there.

mechanix wrote:

on Thursday, November 20th 2008 at 8:00 a.m.

Perhaps the problem is in 2 newlines before [dir= "site"]. Try changing it to a single newline

Stuart Marsh wrote:

on Thursday, November 20th 2008 at 9:33 a.m.

Hi Josh,

Just wanted to say thanks, this worked like a treat.

Cheers

Stuart

Josh wrote:

on Thursday, November 20th 2008 at 9:38 a.m.

Stuart, glad I could help! :)

mechanix, the strange thing is, there actually was only a single newline there, I have no idea why it was showing up as two. I've gotten rid of the newline altogether which seems to have taken care of it, though it's not as pretty as I'd like...

viagra wrote:

on Tuesday, December 23rd 2008 at 6:51 p.m.

Hello, I agree with you! viagra

viagra wrote:

on Friday, December 26th 2008 at 4:32 a.m.

Nice site viagra

:
:
:
:
:
 

copyright © Joshua Ourisman 2006-2009 all rights reserved