Django 迁移和 FileSystemStorage 取决于设置
Django migrations and FileSystemStorage depending on settings
在我的 Django 应用程序中,我对生成的文件使用 FileSystemStorage
。我这样初始化它:
import os
from urlparse import urljoin
from django.conf import settings
from django.core.files.storage import FileSystemStorage
gen_files_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'generated/'), base_url=urljoin(settings.MEDIA_URL, 'generated/'))
当我想创建一个新文件时,我使用:
from django.core.files.base import ContentFile
from django.db import models
def next_number():
# num = ...
return num
gen_file = models.FileField(storage=gen_files_storage)
gen_file.save('file%s.txt' % next_number(), ContentFile(''))
效果很好。唯一的问题是 FileSystemStorage
的路径在 Django 迁移中是 "hardcoded"。因为我在开发(更改)和生产中使用不同的设置,通常 manage.py makemigrations
命令仅因为路径更改而生成迁移,尽管数据库中的所有内容都保持不变。
我知道有一个使用 FileSystemStorage
的子类的解决方案(请参阅下面我的回答),但是有更好的解决方案吗?
有一个解决方案涉及 FileSystemStorage
的自定义 @deconstructible
子类:
import os
from urlparse import urljoin
from django.conf import settings
from django.core.files.storage import FileSystemStorage
from django.utils.deconstruct import deconstructible
@deconstructible
class MyFileSystemStorage(FileSystemStorage):
def __init__(self, subdir):
self.subdir = subdir
super(MyFileSystemStorage, self).__init__(location=os.path.join(settings.MEDIA_ROOT, self.subdir), base_url=urljoin(settings.MEDIA_URL, self.subdir))
def __eq__(self, other):
return self.subdir == other.subdir
然后我可以像这样初始化存储:
import os
from urlparse import urljoin
from django.conf import settings
from django.core.files.storage import FileSystemStorage
gen_files_storage = MyFileSystemStorage('generated/')
这样 Django 迁移就不会注意到我的设置发生变化。还有更好的方法吗?
解决方案是永远不要 运行 makemigrations
生产。 运行 migrate
在生产服务器上你想要的一切,但忽略有关 运行ning makemigrations
的警告,如果它们与此问题有关。
想一想:makemigrations
生成 Python 代码,因此 运行 在生产环境中使用它与在该服务器上开发是一样的。根据您的服务器设置,您的生产站点可能会正确提供这些文件,而不管 makemigrations
警告。
我的问题是相关的,但略有不同。字段使用的存储 class 可以根据设置更改:本地默认,生产中的远程存储。我实现了 FileField
的子 class,它在解构迁移生成字段时忽略了 storage kwarg。
from django.db.models import FileField
class VariableStorageFileField(FileField):
"""
Disregard the storage kwarg when creating migrations for this field
"""
def deconstruct(self):
name, path, args, kwargs = super(VariableStorageFileField, self).deconstruct()
kwargs.pop('storage', None)
return name, path, args, kwargs
可以这样使用:
class MyModel(models.Model):
storage = get_storage_class(getattr(settings, 'LARGE_FILE_STORAGE', None))()
file = VariableStorageFileField(blank=True, null=True, storage=storage)
升级到 Django 3.1+ 修复了这个问题:https://docs.djangoproject.com/en/3.2/releases/3.1/#file-storage
只需将一个可调用对象传递给存储参数即可。
from django.db import models
from django.conf import settings
from django.core.files.storage import get_storage_class
def _get_storage():
storage_class = get_storage_class(settings.MY_STORAGE_CLASS) # ie. 'django.core.files.storage.FileSystemStorage'
return storage_class()
class MyModel(models.Model):
myfile = models.FileField(max_length=255, blank=True, storage=_get_storage)
在我的 Django 应用程序中,我对生成的文件使用 FileSystemStorage
。我这样初始化它:
import os
from urlparse import urljoin
from django.conf import settings
from django.core.files.storage import FileSystemStorage
gen_files_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'generated/'), base_url=urljoin(settings.MEDIA_URL, 'generated/'))
当我想创建一个新文件时,我使用:
from django.core.files.base import ContentFile
from django.db import models
def next_number():
# num = ...
return num
gen_file = models.FileField(storage=gen_files_storage)
gen_file.save('file%s.txt' % next_number(), ContentFile(''))
效果很好。唯一的问题是 FileSystemStorage
的路径在 Django 迁移中是 "hardcoded"。因为我在开发(更改)和生产中使用不同的设置,通常 manage.py makemigrations
命令仅因为路径更改而生成迁移,尽管数据库中的所有内容都保持不变。
我知道有一个使用 FileSystemStorage
的子类的解决方案(请参阅下面我的回答),但是有更好的解决方案吗?
有一个解决方案涉及 FileSystemStorage
的自定义 @deconstructible
子类:
import os
from urlparse import urljoin
from django.conf import settings
from django.core.files.storage import FileSystemStorage
from django.utils.deconstruct import deconstructible
@deconstructible
class MyFileSystemStorage(FileSystemStorage):
def __init__(self, subdir):
self.subdir = subdir
super(MyFileSystemStorage, self).__init__(location=os.path.join(settings.MEDIA_ROOT, self.subdir), base_url=urljoin(settings.MEDIA_URL, self.subdir))
def __eq__(self, other):
return self.subdir == other.subdir
然后我可以像这样初始化存储:
import os
from urlparse import urljoin
from django.conf import settings
from django.core.files.storage import FileSystemStorage
gen_files_storage = MyFileSystemStorage('generated/')
这样 Django 迁移就不会注意到我的设置发生变化。还有更好的方法吗?
解决方案是永远不要 运行 makemigrations
生产。 运行 migrate
在生产服务器上你想要的一切,但忽略有关 运行ning makemigrations
的警告,如果它们与此问题有关。
想一想:makemigrations
生成 Python 代码,因此 运行 在生产环境中使用它与在该服务器上开发是一样的。根据您的服务器设置,您的生产站点可能会正确提供这些文件,而不管 makemigrations
警告。
我的问题是相关的,但略有不同。字段使用的存储 class 可以根据设置更改:本地默认,生产中的远程存储。我实现了 FileField
的子 class,它在解构迁移生成字段时忽略了 storage kwarg。
from django.db.models import FileField
class VariableStorageFileField(FileField):
"""
Disregard the storage kwarg when creating migrations for this field
"""
def deconstruct(self):
name, path, args, kwargs = super(VariableStorageFileField, self).deconstruct()
kwargs.pop('storage', None)
return name, path, args, kwargs
可以这样使用:
class MyModel(models.Model):
storage = get_storage_class(getattr(settings, 'LARGE_FILE_STORAGE', None))()
file = VariableStorageFileField(blank=True, null=True, storage=storage)
升级到 Django 3.1+ 修复了这个问题:https://docs.djangoproject.com/en/3.2/releases/3.1/#file-storage
只需将一个可调用对象传递给存储参数即可。
from django.db import models
from django.conf import settings
from django.core.files.storage import get_storage_class
def _get_storage():
storage_class = get_storage_class(settings.MY_STORAGE_CLASS) # ie. 'django.core.files.storage.FileSystemStorage'
return storage_class()
class MyModel(models.Model):
myfile = models.FileField(max_length=255, blank=True, storage=_get_storage)