代码之家  ›  专栏  ›  技术社区  ›  onekiloparsec

如何将django filefield与动态amazon s3 bucket结合使用?

  •  2
  • onekiloparsec  · 技术社区  · 7 年前

    我有一个带有filefield的django模型,还有一个使用amazon s3 bucket的默认存储(通过 django-storage )

    我的问题不是将文件上传到动态文件夹路径(正如我们在许多其他答案中看到的那样)。我的问题有两个方面:

    • 文件已经在amazon s3存储桶中,我不想下载reupload(更糟的是:我只能读取它们)。
    • 文件可以通过不同文件的s3凭据访问(也就是说,文件可以位于不同的存储桶中,并且可以通过不同的凭据访问)。因此, 我的文件域必须具有动态存储 .

    知道吗?

    (djabgo 1.11,python 3)。

    1 回复  |  直到 7 年前
        1
  •  0
  •   onekiloparsec    7 年前

    原来没那么难。 但下面的代码没有经过太多测试 ,而且我必须警告你,不检查就不要复制粘贴!

    我创造了一个习惯 FileField 子类:

    class DynamicS3BucketFileField(models.FileField):
        attr_class = S3Boto3StorageFile
        descriptor_class = DynamicS3BucketFileDescriptor
    
        def pre_save(self, model_instance, add):
            return getattr(model_instance, self.attname)
    

    请注意 attr_class 特别是使用 S3Boto3StorageFile (A)级 File 子类由提供 django-storages )

    这个 pre_save 重载只有一个目标:避免内部 file.save 尝试重新上载文件的调用。

    魔法发生在 FileDescriptor 子类:

    class DynamicS3BucketFileDescriptor(FileDescriptor):
        def __get__(self, instance, cls=None):
            if instance is None:
                return self
    
            # Copied from FileDescriptor
            if self.field.name in instance.__dict__:
                file = instance.__dict__[self.field.name]
            else:
                instance.refresh_from_db(fields=[self.field.name])
                file = getattr(instance, self.field.name)
    
            # Make sure to transform storage to a Storage instance.
            if callable(self.field.storage):
                self.field.storage = self.field.storage(instance)
    
            # The file can be a string here (depending on when/how we access the field).
            if isinstance(file, six.string_types):
                # We instance file following S3Boto3StorageFile constructor.
                file = self.field.attr_class(file, 'rb', self.field.storage)
                # We follow here the way FileDescriptor work (see 'return' finish line).
                instance.__dict__[self.field.name] = file
    
            # Copied from FileDescriptor. The difference here is that these 3
            # properties are set systematically without conditions.
            file.instance = instance
            file.field = self.field
            file.storage = self.field.storage
            # Added a very handy property to file.
            file.url = self.field.storage.url(file.name)
    
            return instance.__dict__[self.field.name]
    

    上面的代码采用了一些适合我的情况的filedescriptor内部代码。注意 if callable(self.field.storage): ,解释如下。

    关键是: file = self.field.attr_class(file, 'rb', self.field.storage) ,它会自动创建 S3boto3存储文件 取决于电流的内容 file 实例(有时是一个文件,有时是一个简单的字符串,这是filedescriptor业务的一部分)。

    现在,动态部分非常简单。实际上,在声明文件字段时,可以向 storage 选项,函数。这样地:

    class MyMedia(models.Model):
        class Meta:
            app_label = 'appname'
    
        mediaset = models.ForeignKey(Mediaset, on_delete=models.CASCADE, related_name='media_files')
        file = DynamicS3BucketFileField(null=True, blank=True, storage=get_fits_file_storage)
    

    以及功能 get_fits_file_storage 将用一个参数调用:的实例 MyMedia . 因此,我可以使用该对象的任何属性来返回有效存储。就我而言 mediaset ,其中包含一个密钥,该密钥允许我检索包含s3凭据的对象,使用s3凭据可以构建 S3Boto3Storage 实例(由 Django仓库 )

    明确地:

    def get_fits_file_storage(instance):
        name = instance.mediaset.archive_storage_name
        return instance.mediaset.archive.bucket_keys.get(name= name).get_storage()
    

    等一下!