Django: Any way to change "upload_to" property of FileField without resorting to magic?

Question:

See this blog post… It’s quite old so maybe things have changed. But in my experimenting they have not. In order to change the model field FileField upload_to path dynamically, you must resort to using signals and creating custom model fields. Nasty. I can’t imagine that having a dynamic upload path is such a special use case that it’s not addressed by the standard Django framework? Am I missing something? Is there any other way to accomplish this?

In essence I want to do this:

def MyModel(models.Model):
    fileUpload = models.FileField(upload_to='media/', null=True, blank=True)

def save(self, **kwargs):
    # Retrieve the user's id/pk from their profile
    up = UserProfile.objects.get(email=self.email)

    # All their uploads go into their own directory
    self.file_image.upload_to = up.id

    super(MyModel, self).save()

However, in the 10 different implementations I tried, Django hates all of them. For this one in particular, the file is uploaded to the default path 'media/'.

I’ve tried scraping a modelform for parameters and passing those parameters into a dict object, create a MyModel object, set the MyModel.fileUpload.upload_to parameter, then copy the dict into the model and save. Doesn’t work.

I also tried to override the __init__ method, but guess what? That is so early in the object creation that it doesn’t actually have self.email defined yet! So that doesn’t work.

Any ideas or must I follow the arcane solution outlined in the original link?

Asked By: James

||

Answers:

You can use this combination in your models.py file for upload_to:

def content_file_name(instance, filename):
    return '/'.join(['content', filename])

class Book(models.Model):
    title = models.CharField(max_length=80)
    file = models.FileField(upload_to=content_file_name, null=False, verbose_name="File")

You can change the word ‘content’ to any string that you want to use to name the directory that it’s going to save the file to. So in this case the path would be: /media/content/<file_name>

Answered By: Aaron Lelevier

So there is actually a fairly easy solution to this, in the FileField field, the upload_to keyword argument can actually take a function as a parameter. The function that you specify in your upload_to kwarg should have this signature.

def my_awesome_upload_function(instance, filename):
    """ this function has to return the location to upload the file """
 
     return os.path.join('/media/%s/' % instance.id, filename)

In this case, instance is the instance of your model that has the FileField, and filename is the filename of the uploaded file. So your model like in your example above would look like this:

def MyModel(models.Model):
    fileUpload = models.FileField(upload_to=my_awesome_upload_function, null=True, blank=True)

If this makes sense you can now change my_awesome_upload_function to generate you path to upload the file to based on your preference, given the model instance and filename of the file that has been uploaded.

Answered By: mgrouchy

Same blog, that I referenced before, also mentioned the newer solution.

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)

Here he uses the id of the actual object. Of course you can use whatever you want. But this answers my question.

Answered By: James
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.