Django 仅在生产环境中使用私有 S3 存储

Django use private S3 storage only in production environment

我已经将我的 django REST API 设置为在 DEBUG 模式下使用本地存储,在生产环境中使用 S3 存储。这适用于 public 文件,因为我像这样覆盖 DEFAULT_FILE_STORAGE

if IS_DEBUG:
    DEFAULT_FILE_STORAGE = 'api.storage_backends.PublicMediaStorage'

并且每个 FileField 都会自动使用它。现在我想以同样的方式使用私有 S3 存储,但是因为我必须明确定义存储 (FileField(storage=PrivateMediaStorage())),所以始终使用 S3 存储。

如何在 DEBUG 模式下使用本地存储而不是 S3 存储?

PS:我已经考虑过根据 DEBUG 模式更改模型以使用带或不带显式存储的 FileField。这并没有完全解决我的问题,因为我的迁移是在 DEBUG 模式下创建的,因此总是包含没有私有存储的模型 class.

更新: 我正在寻找一种可以在两个环境中共享相同迁移并且仅在运行时延迟实例化实际存储 class 的解决方案。就像 django 已经处理了 DEFAULT_FILE_STORAGE

最佳解决方案是使用 FileField 而不显式 storageclass.

# settings.py

if DEBUG:
    DEFAULT_FILE_STORAGE = 'api.storage_backends.PublicMediaStorage'
else:
    DEFAULT_FILE_STORAGE = 'api.storage_backends.PrivateMediaStorage'


# models.py
class Foo(models.Model):
    file = models.FileField() # without storage

在文件上传过程中,Django 会以 lazy 的方式调用 DEFAULT_FILE_STORAGE class。

备注

这些设置不会创建带有 storage 参数

的迁移文件

更新-1

如果您想更好地控制存储,请创建您自己的自定义文件字段并在模型中连接

<b>def get_storage():
    """
    Change this function to whatever way as you need
    """
    from api.storage_backends import PublicMediaStorage, PrivateMediaStorage
    if DEBUG:
        return PublicMediaStorage()
    else:
        return PrivateMediaStorage()</b>


<b>class CustomFileField(models.FileField):
    def __init__(self, *args, **kwargs):
        kwargs['storage'] = get_storage() # calling external function
        super().__init__(*args, **kwargs)</b>


class Foo(models.Model):
    file = <b>CustomFileField() # use custom filefield here</b>

听起来棘手的部分是在一个项目中同时拥有 publicprivate 媒体存储。

下面的示例假设您使用的是 django storages,但无论如何该技术都应该有效。

通过扩展 S3BotoStorage class.

定义私有存储

如果使用 S3,谨慎的做法是将 privatepublic public 存储在不同的 S3 中水桶。此自定义存储允许您通过设置指定此参数。

# yourapp.custom_storage.py

from django.conf import settings
from django.core.files.storage import get_storage_class
from storages.backends.s3boto import S3BotoStorage

class S3PrivateStorage(S3BotoStorage):
    """
    Optional   
    """
    default_acl = "private"               # this does the trick

    def __init__(self):
        super(S3PrivateStorage, self).__init__()
        self.bucket_name = settings.S3_PRIVATE_STORAGE_BUCKET_NAME


# important
private_storage_class = get_storage_class(settings.PRIVATE_FILE_STORAGE)

private_storage = private_storage_class() # instantiate the storage

重要的部分是该文件的最后两行 - 它声明 private_storage 用于您的 FileField:

from yourappp.custom_storage import private_storage
...
class YourModel(Model):

    the_file = models.FileField(
                   upload_to=..., 
                   storage=private_storage)
...

最后,在您的设置文件中,应该这样做。

# settings.py

if DEBUG:
    # In debug mode, store everything on the filestystem
    DEFAULT_FILE_STORAGE = 'django.files.storage.FileSystemStorage'
    PRIVATE_FILE_STORAGE = 'django.files.storage.FileSystemStorage'
else:
    # In production store public things using S3BotoStorage and private things
    # in a custom storage
    DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
    PRIVATE_FILE_STORAGE = 'yourapp.custom_storage.S3PrivateStorage'

作为最后一条不请自来的建议:将存储设置与 DEBUG 模式 分离并允许在环境变量中指定上述所有参数通常很有用。在某些时候,您可能希望 运行 使用类似生产的存储配置将您的应用程序置于调试模式。

Thomas 接受的答案近乎完美。当您使用不同的本地开发和生产设置时,它会出现一个小的迁移问题。

假设您在本地环境中将 storage 设置为 FileSystemStorage,在生产环境中将 S3PrivateStorage 设置为 S3PrivateStorage。如果您在本地环境中 运行 makemigrations,迁移文件会将您的 FileField 的存储字段设置为与您在 运行 makemigrations 中不同的值生产环境。

幸运的是,Django 3.1 的一项新功能使我们能够通过对 Thomas 的回答稍作改动轻松解决此问题。我们不使用 private_storage,它是存储 class 的 实例 ,而是使用 you can use a callable as storage 并创建一个函数,它将 return 适当的存储。

那么,代码(改编自 Thomas 的回答)将是:

# yourapp.custom_storage.py

from django.conf import settings
from django.core.files.storage import get_storage_class
from storages.backends.s3boto import S3BotoStorage

class S3PrivateStorage(S3BotoStorage):
    """
    Optional   
    """
    default_acl = "private"               # this does the trick

    def __init__(self):
        super(S3PrivateStorage, self).__init__()
        self.bucket_name = settings.S3_PRIVATE_STORAGE_BUCKET_NAME

def select_private_storage():
    # important
    private_storage_class = get_storage_class(settings.PRIVATE_FILE_STORAGE)
    return private_storage_class() # instantiate the storage

然后在您的字段中相应地设置存储

from yourappp.custom_storage import select_private_storage
...
class YourModel(Model):

    the_file = models.FileField(
        upload_to=..., 
        storage=select_private_storage # notice we're using the callable
    )
...