Reputation: 8774
In my Django app I use a FileSystemStorage
for generated files. I initialize it like this:
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/'))
When I want to create a new file I use:
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(''))
That works fine. The only problem is that the FileSystemStorage
's path is "hardcoded" in the Django migration. Because I use different settings for development (which changes) and production, often the manage.py makemigrations
command generates a migration only because the path changed, although everything stays the same in the database.
I know there is a solution using a subclass of FileSystemStorage
(see my answer below), but is there a better solution?
Upvotes: 23
Views: 4735
Reputation: 153
Upgrading to Django 3.1+ fixes this: https://docs.djangoproject.com/en/3.2/releases/3.1/#file-storage
Just pass a callable into the storage argument.
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)
Upvotes: 9
Reputation: 868
My problem was related, but slightly different. The storage class used by the field can change based on settings: the default locally, remote storage in production. I implemented a subclass of FileField
that ignores the storage kwarg when deconstructing the field for migration generation.
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
It can be used like this:
class MyModel(models.Model):
storage = get_storage_class(getattr(settings, 'LARGE_FILE_STORAGE', None))()
file = VariableStorageFileField(blank=True, null=True, storage=storage)
Upvotes: 2
Reputation: 2962
The solution is to never run makemigrations
on production. Run migrate
all you want on production servers, but ignore warnings about running makemigrations
if they pertain to this issue.
Think about it: makemigrations
generates Python code, so running it on production would be the same as developing on that server. Depending on your server setup, your production site will likely serve those files correctly regardless of the makemigrations
warning.
Upvotes: 2
Reputation: 8774
There is a solution involving a custom @deconstructible
subclass of FileSystemStorage
:
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
Then I can initialize the storage like this:
import os
from urlparse import urljoin
from django.conf import settings
from django.core.files.storage import FileSystemStorage
gen_files_storage = MyFileSystemStorage('generated/')
This way Django migrations won't notice changes in my settings. Is there a better way though?
Upvotes: 11