17

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)
1

3 Answers 3

30

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)
Sign up to request clarification or add additional context in comments.

Comments

7

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

2 Comments

When filename is two words, e.g. NRB 2708, it ends up as NRB_2708. How can I avoid/override the underscore to end up with NRB 2708?
@Gathide return filename.replace('_', ' ')
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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.