pythondjangodjango-storage

How to programmatically upload local file as Django model field?


I'm having troubles trying to upload files to FileField from local path.

I have correctly configurred CDN backend in S3 bucket and use it as PrivateMediaStorage for one of my model fields:

class MyModel(models.Model):
    some_file = models.FileField(storage=PrivateMediaStorage())
    ...

With this very simple configuration whenever I'm creating/updating model through django-admin it is saved and file attached as some_file is correctly uploaded to S3 bucket.

Yet if I try to create/update model instance programmatically, say through custom manage.py command, model instance itself is created but attachment is never uploaded to CDN. Here's simplified version of code I'm using to upload files:

class Command(BaseCommand):
    help = 'Creates dummy instance for quicker configuration'

    def handle(self, *args, **options):
        some_file = os.path.join(os.path.dirname(__file__), '../../../temporary/some_image.png')

        if not os.path.exists(some_file):
            raise CommandError(f'File {some_file} does not exist')
        else: 
            instance, created = MyModel.objects.get_or_create(defaults={'some_file': some_file}, ...)

What is missing in my implementation and what needs to be adjusted to allow file uploads from local storage?


Solution

  • You're passing a string (the result of os.path.join()) to your some_file field, but you need to pass it an actual File object.

    The easiest way to save a file on a model directly is to use the FieldFile's save() method.

    As a working solution for case provided in question a valid way of creating a record would be:

    instance = MyModel.objects.create(some_file=File(file=open(some_file, 'rb'), name='some_name.png'))
    

    Or even better to use pathlib to obtain name dynamically:

    from pathlib import Path
    
    instance = MyModel.objects.create(some_file=File(file=open(some_file, 'rb'), name=Path(some_file).name))
    

    Note that fetching a row based on the file is unlikely to work, AFAIK each time you open a file, doing a get_or_create() with the File instance as argument will probably create a new row each time. Better put file fields into defaults:

    with open(some_file, 'rb') as file:
        instance, created = MyModel.objects.get_or_create(
            some_other_field=..., 
            defaults={'some_file': File(
                file=file, 
                name=pathlib.Path(some_file).name
                )}
        )