Django dynamic FileField upload_to
Asked Answered
P

3

16

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)
Pantoja answered 29/5, 2018 at 19:6 Comment(0)
F
27

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)
Fango answered 29/5, 2018 at 19:36 Comment(0)
G
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

Gabble answered 29/5, 2018 at 19:37 Comment(2)
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?Cutlor
@Cutlor return filename.replace('_', ' ')Gabble
D
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)
Diastrophism answered 5/7, 2021 at 15:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.