django: playing with upload_to field

Django provides models.FileField & models.ImageField to save uploaded files & images respectively. Both these fields take a parameters ‘upload_to’ which defines the path where the uploaded file will be saved (complete path is settings.MEDIA_ROOT + upload_to). You can play some neat tricks with this little feature.

Creating the date based folders at run time

You can use the upload_to parameter to upload files in folders named on the file date.

Suppose you are creating a photo album and want images to be uploaded in folders containing the year and month in name. This is convenient for files management as well as for backups.

class Photo(models.Model):
    original = models.ImageField(upload_to='album/%m-%Y/')
    #other fields

Now every time you save an image using Photo, django will create (if not already exists) a folder ‘MM-YYYY’ in MEDIA_ROOT/album/ and save the image in it.

Dynamically changing the folder

What if you want to change the upload_to path at run time? Consider the following model,

class MediaContent(models.Model):
    content_file = models.FileField()
    content_type = models.CharField(max_length=1,
        choices=(('I','Image'),('V','Video'),('A','Audio'),('D','Doc')))
    #other fields

The MediaContent file caters for various media content files like image, video, audio, docs and pdf files. I want to treat all media files in the same manner but upload these files to different folders based on the content type. This means that I need to change upload_to before I want to save a MediaContent instance.

Using a callback as upload_to

django enables you to provide a callback as upload_to value. This callable is nothing more than a function that takes following arguments,

  • self – instance of MediaContent
  • instance – instance of actual FileField or ImageField (content_file in this case)
  • filename – the file name that was passed while saving the file
Using this technique, the implementation would look like this,
class MediaContent(models.Model):
    content_file = models.FileField(upload_to=get_media_upload_to)
    content_type = models.CharField(max_length=1,
        choices=(('I','Image'),('V','Video'),('A','Audio'),('D','Doc')))
    #other fields
def get_media_upload_to(instance, filename):
    """
    A simple way to return the full path      
    """
    paths = { 'I':'images/', 'V':'videos/', 'A':'audio/', 'D':'documents'/ }
    return settings.MEDIA_ROOT + 'content/' + paths[instance.content_type] + filename

Every time you call media_content_object.content_file.save(filename, mem_file), get_media_upload_to will be called and you can determine the file path based on content_type

Modify the upload_to at run-time

You can set upload_to to a fixed value and then change it to your desired location just before saving the object.

def save_content(request):
    #if image
        MediContent._meta.get_field('content_file').upload_to='content/images/'
    #if video
        MediContent._meta.get_field('content_file').upload_to='content/videos/'
    #if audio
        MediContent._meta.get_field('content_file').upload_to='content/audios/'
    #if doc
        MediContent._meta.get_field('content_file').upload_to='content/documents/'
    #if pdf
        MediContent._meta.get_field('content_file').upload_to='content/acrobat/'
    #other types

Edit: It seems that in the latest version of django, self is not passed to the get_media_upload_to function, which I have fixed

Related posts:

  1. django: Hacking django object_list to take list of templates

One Comment

  1. Alex UK says:

    I was trying to repeat this examples and failed.
    I am curious to see full code and version of the django your are using. In my case only instance, filename will be passed to get_media_upload_to.
    Any hints?

Leave a Reply