How to dynamically select storage option for model

2019-09-09 14:01发布

Depending on the file extension, I want the file to be stored in a specific AWS bucket. I tried passing a function to the storage option, similar to how upload_to is dynamically defined.

However, this doesn't give the desired results. In my template, when I try href to document.docfile.url, the link doesn't work.

Checking in the shell, this happens

Document.objects.all()[0].docfile.storage.bucket
 <Bucket: <function aws_bucket at 0x110672050>>

Document.objects.all()[0].docfile.storage.bucket_name
 <function myproject.myapp.models.aws_bucket>

Desired behaviour would be

Document.objects.all()[0].docfile.storage.bucket_name
 'asynch-uploader-txt'
Document.objects.all()[0].docfile.storage.bucket     
 <Bucket: asynch-uploader-txt>

This is my models.py file:

# -*- coding: utf-8 -*-
from django.db import models
from storages.backends.s3boto import S3BotoStorage

def upload_file_to(instance, filename):
    import os
    from django.utils.timezone import now
    filename_base, filename_ext = os.path.splitext(filename)
    return 'files/%s_%s%s' % (
        filename_base,
        now().strftime("%Y%m%d%H%M%S"),
        filename_ext.lower(),
    )

def aws_bucket(instance, filename):
    import os
    filename_base, filename_ext = os.path.splitext(filename)
    return 'asynch-uploader-%s' %(filename_ext[1:])

class Document(models.Model):
    docfile = models.FileField(upload_to=upload_file_to,storage=S3BotoStorage(bucket=aws_bucket))

Why is aws_bucket getting passed as a function and not a string, the way that upload_file_to is? How can I correct it?

1条回答
Juvenile、少年°
2楼-- · 2019-09-09 14:53

For what you're trying to do you may be better off making a custom storage backend and just overriding the various bits of S3BotoStorage. In particular if you make bucket_name a property you should be able to get the behavior you want.

EDIT:

To expand a bit on that, the source for S3BotoStorage.__init__ has the bucket as an optional argument. Additionally bucket when it's used in the class is a @param, making it easy to override. The following code is untested, but should be enough to give you a starting point

class MyS3BotoStorage(S3BotoStorage):
    @property
    def bucket(self):
        if self._filename.endswith('.jpg'):
            return self._get_or_create_bucket('SomeBucketName')
        else:
            return self._get_or_create_bucket('SomeSaneDefaultBucket')

    def _save(self, name, content):
        # This part might need some work to normalize the name and all...
        self._filename = name
        return super(MyS3BotoStorage, self)._save(name, content)
查看更多
登录 后发表回答