13

I'm trying to make dynamic upload path to FileField model. So when user uploads a file, Django stores it to my computer /media/(username)/(path_to_a_file)/(filename).

E.g. /media/Michael/Homeworks/Math/Week_1/questions.pdf or /media/Ernie/Fishing/Atlantic_ocean/Good_fishing_spots.txt

VIEWS
@login_required
def add_file(request, **kwargs):
if request.method == 'POST':
    form = AddFile(request.POST, request.FILES)
    if form.is_valid():
        post = form.save(commit=False)
        post.author = request.user

        post.parent = Directory.objects.get(directory_path=str(kwargs['directory_path']))
        post.file_path = str(kwargs['directory_path'])

        post.file_content = request.FILES['file_content'] <-- need to pass dynamic file_path here

        post.save()
        return redirect('/home/' + str(post.author))

MODELS
class File(models.Model):
    parent = models.ForeignKey(Directory, on_delete=models.CASCADE)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    file_name = models.CharField(max_length=100)
    file_path = models.CharField(max_length=900)
    file_content = models.FileField(upload_to='e.g. /username/PATH/PATH/..../')

FORMS
class AddFile(forms.ModelForm):
    class Meta:
        model = File
        fields = ['file_name', 'file_content']

What I have found was this, but after trial and error I have not found the way to do it. So the "upload/..." would be post.file_path, which is dynamic.

def get_upload_to(instance, filename):
    return 'upload/%d/%s' % (instance.profile, filename)


class Upload(models.Model):
    file = models.FileField(upload_to=get_upload_to)
    profile = models.ForeignKey(Profile, blank=True, null=True)
Jertzuuka
  • 163
  • 1
  • 1
  • 5

3 Answers3

26

You can use some thing like this(i used it in my project):

import os
def get_upload_path(instance, filename):
    return os.path.join(
      "user_%d" % instance.owner.id, "car_%s" % instance.slug, filename)

Now:

photo = models.ImageField(upload_to=get_upload_path)
fqxp
  • 7,680
  • 3
  • 24
  • 41
6

Since the file_path is an attribute on the File model, can you not build the full path something like this:

import os

def create_path(instance, filename):
    return os.path.join(
        instance.author.username,
        instance.file_path,
        filename
    )

And then reference it from your File model:

class File(models.Model):
    ...
    file_content = models.FileField(upload_to=create_path)

Link to docs

Will Keeling
  • 22,055
  • 4
  • 51
  • 61
4

The other answers work flawlessly; however, I want to point out the line in the source code that allows such functionality. You can view the function, generate_filename, here, in Django's source code.

The lines that make the magic happen:

if callable(self.upload_to):
    filename = self.upload_to(instance, filename)

When you pass a callable to the upload_to parameter, Django will call the callable to generate the path. Note that Django expects your callable to handle two arguments:

  • instance
    • the model that contains the FileField/ImageField
  • filename
    • the name of the uploaded file, including the extension (.png, .pdf, ...)

Also note that Python does not force your callable's arguments to be exactly 'instance' and 'filename' because Django passes them as positional parameters. For example, I prefer to rename them:

def get_file_path(obj, fname):
    return os.path.join(
        'products',
        obj.slug,
        fname,
    )

And then use it like so:

image = models.ImageField(upload_to=get_file_path)
gmdev
  • 2,725
  • 2
  • 13
  • 28