如何将 Django FileField 与动态 Amazon S3 存储桶一起使用?
How to use Django FileField with dynamic Amazon S3 bucket?
我有一个带有 Filefield 的 Django 模型,以及一个使用 Amazon S3 存储桶的默认存储(通过出色的 django-storage)。
我的问题是无法将文件上传到动态文件夹路径(正如我们在许多其他答案中看到的那样)。我的问题更深层次和双重:
- 文件已经在 Amazon S3 存储桶中,我不想下载-重新上传它们(更糟的是:我对它们只有读取权限)。
- 可以通过 S3 凭据访问文件,这些凭据可能因文件而异(也就是说,文件可以位于不同的存储桶中,并通过不同的凭据进行访问)。因此,我的 FileField 必须具有动态存储。
有什么想法吗?
(Djabgo 1.11, Python 3).
原来并没有那么难。 但是下面的代码没有经过太多测试,我必须警告你不要在没有检查的情况下复制粘贴!
我创建了一个自定义 FileField
subclass:
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
class(django-storages
提供的 File
subclass)。
pre_save
重载只有一个目标:避免尝试重新上传文件的内部 file.save
调用。
魔法发生在 FileDescriptor
subclass:
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)
,根据当前file
实例的内容自动创建S3Boto3StorageFile
的有效实例(有时是文件,有时是一个简单的字符串,这是 FileDescriptor 业务的一部分)。
现在,动态部分非常简单。事实上,当声明一个FileField时,你可以提供给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
的实例。因此,我可以使用该对象的任何 属性 到 return 有效存储。在我的例子中 mediaset
,它包含一个密钥,允许我检索包含 S3 凭据的对象,我可以用它构建一个 S3Boto3Storage
实例(另一个 class 由 django-storages
提供) .
具体来说:
def get_fits_file_storage(instance):
name = instance.mediaset.archive_storage_name
return instance.mediaset.archive.bucket_keys.get(name= name).get_storage()
瞧瞧!
我有一个带有 Filefield 的 Django 模型,以及一个使用 Amazon S3 存储桶的默认存储(通过出色的 django-storage)。
我的问题是无法将文件上传到动态文件夹路径(正如我们在许多其他答案中看到的那样)。我的问题更深层次和双重:
- 文件已经在 Amazon S3 存储桶中,我不想下载-重新上传它们(更糟的是:我对它们只有读取权限)。
- 可以通过 S3 凭据访问文件,这些凭据可能因文件而异(也就是说,文件可以位于不同的存储桶中,并通过不同的凭据进行访问)。因此,我的 FileField 必须具有动态存储。
有什么想法吗?
(Djabgo 1.11, Python 3).
原来并没有那么难。 但是下面的代码没有经过太多测试,我必须警告你不要在没有检查的情况下复制粘贴!
我创建了一个自定义 FileField
subclass:
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
class(django-storages
提供的 File
subclass)。
pre_save
重载只有一个目标:避免尝试重新上传文件的内部 file.save
调用。
魔法发生在 FileDescriptor
subclass:
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)
,根据当前file
实例的内容自动创建S3Boto3StorageFile
的有效实例(有时是文件,有时是一个简单的字符串,这是 FileDescriptor 业务的一部分)。
现在,动态部分非常简单。事实上,当声明一个FileField时,你可以提供给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
的实例。因此,我可以使用该对象的任何 属性 到 return 有效存储。在我的例子中 mediaset
,它包含一个密钥,允许我检索包含 S3 凭据的对象,我可以用它构建一个 S3Boto3Storage
实例(另一个 class 由 django-storages
提供) .
具体来说:
def get_fits_file_storage(instance):
name = instance.mediaset.archive_storage_name
return instance.mediaset.archive.bucket_keys.get(name= name).get_storage()
瞧瞧!