Alejandro Franco
Alejandro Franco

Reputation: 333

Django get and use instance and field name on upload_to

I'm trying to build a path for a FileField, getting and using the instance to get another data for the URL, plus the field name, to get something like:

/media/documents/<instance_data>/<field_name>.pdf

My best working approach is:

class UserDocFileField(models.FileField):

    def get_fixed_folder_path(self, instance, filename):
        return 'documents/{}/{}.pdf'.format(instance.user.rfc, self.name)

    def __init__(self, *args, **kwargs):
        kwargs["upload_to"] = self.get_fixed_folder_path
        super(UserDocFileField, self).__init__(*args, **kwargs)

And in my model:

class Documents(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
    file_1 = UserDocFileField()
    file_2 = UserDocFileField()
    # ... other documents

Giving me what I'm looking for, i.e.:

/media/documents/ABCD840422ABC/file_1.pdf

However, this makes Django to generate a migration file every single time I run makemigrations, I have tried to set it as an inner class, rewriting the super as

super(Documents.UserDocFileField, self).__init__(*args, **kwargs)

But, I got this error:

NameError: name 'Documents' is not defined

So, is there a way to avoid the generations of migrations files or a better approach to solve this?

Upvotes: 1

Views: 2274

Answers (2)

Leonardo Barros
Leonardo Barros

Reputation: 1

For solve my question and create a folder based in user I use this method:

import os, uuid

def get_upload_path(instance, filename):
    '''Split the name of ext, create a new name with uuid and return the path'''
    ext = '.' + filename.split('.')[1]
    filename = f"{uuid.uuid1()}" + ext
    upload_path = os.path.join('images', 'user', str(instance.user.pk), filename)
    return upload_path

Upvotes: 0

Daniel Roseman
Daniel Roseman

Reputation: 599698

One way of doing this is to use a custom class for the upload_to itself, with a __call__ method to make the instance callable. In order to make that serializable for migrations you then need to add a deconstruct method. So:

class UploadTo:
  def __init__(self, name):
    self.name = name

  def __call__(self, instance, filename):
    return 'documents/{}/{}.pdf'.format(instance.user.rfc, self.name)

  def deconstruct(self):
    return ('myapp.models.UploadTo', [self.name], {})

class Documents(models.Model):
  user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
  file_1 = FileField(upload_to=UploadTo('file_1'))
  file_2 = FileField(upload_to=UploadTo('file_2'))

Honestly though, at this point I'd probably just write separate upload_to functions for each field.

Upvotes: 6

Related Questions