A screenshot (portrait view) of my IDE and Traceback showing all the code pasted here, may be easier to read if you have a vertical monitor.
Context: Trying to save image from a URL to a Django ImageField
hosted on EC2 with files on S3 using S3BotoStorage. I'm confused because all of this suggests that Django is still treating it like local storage, while it should S3.
The lines in question that seem to be causing the error:
def get_filename(self, filename):
return os.path.normpath(self.storage.get_valid_name(os.path.basename(filename)))
def get_valid_name(self, name):
"""
Returns a filename, based on the provided filename, that's suitable for
use in the target storage system.
"""
return get_valid_filename(name)
TypeError Exception: get_valid_name() missing 1 required positional argument: 'name'
Last Local vars Tracback before error at get_valid_name
:
filename 'testimagefilename'
self <django.db.models.fields.files.ImageField: image>
(Only the stuff inside these two horizontal dividers is from me, the rest is from Django 1.9)
image.image.save('testimagefilename', File(temp), save=True)
Local vars from Traceback at that point (not sure about the ValueError
on image
, I think it's because it hasn't been created yet):
File <class 'django.core.files.base.File'>
image Error in formatting: ValueError: The 'image' attribute has no file associated with it.
requests <module 'requests' from '/usr/local/lib/python3.4/site-packages/requests/__init__.py'>
Image <class 'mcmaster.models.Image'>
NamedTemporaryFile <function NamedTemporaryFile at 0x7fb0e1bb0510>
temp <tempfile._TemporaryFileWrapper object at 0x7fb0dd241ef0>
Relevant snippets of Django source code:
files.py
def save(self, name, content, save=True):
name = self.field.generate_filename(self.instance, name)
if func_supports_parameter(self.storage.save, 'max_length'):
self.name = self.storage.save(name, content, max_length=self.field.max_length)
else:
warnings.warn(
'Backwards compatibility for storage backends without '
'support for the `max_length` argument in '
'Storage.save() will be removed in Django 1.10.',
RemovedInDjango110Warning, stacklevel=2
)
self.name = self.storage.save(name, content)
setattr(self.instance, self.field.name, self.name)
# Update the filesize cache
self._size = content.size
self._committed = True
# Save the object because it has changed, unless save is False
if save:
self.instance.save()
save.alters_data = True
def get_directory_name(self):
return os.path.normpath(force_text(datetime.datetime.now().strftime(force_str(self.upload_to))))
def get_filename(self, filename):
return os.path.normpath(self.storage.get_valid_name(os.path.basename(filename)))
def generate_filename(self, instance, filename):
# If upload_to is a callable, make sure that the path it returns is
# passed through get_valid_name() of the underlying storage.
if callable(self.upload_to):
directory_name, filename = os.path.split(self.upload_to(instance, filename))
filename = self.storage.get_valid_name(filename)
return os.path.normpath(os.path.join(directory_name, filename))
return os.path.join(self.get_directory_name(), self.get_filename(filename))
storage.py
def get_valid_name(self, name):
"""
Returns a filename, based on the provided filename, that's suitable for
use in the target storage system.
"""
return get_valid_filename(name)
text.py
def get_valid_filename(s):
"""
Returns the given string converted to a string that can be used for a clean
filename. Specifically, leading and trailing spaces are removed; other
spaces are converted to underscores; and anything that is not a unicode
alphanumeric, dash, underscore, or dot, is removed.
>>> get_valid_filename("john's portrait in 2004.jpg")
'johns_portrait_in_2004.jpg'
"""
s = force_text(s).strip().replace(' ', '_')
return re.sub(r'(?u)[^-\w.]', '', s)
get_valid_filename = allow_lazy(get_valid_filename, six.text_type)
I'd make a guess you didn't instantiate the Storage class. How are you setting Django to use the custom storage? If you do this in models.py
image = models.ImageField(storage=MyStorage)
It will fail exactly as you describe. It should be
image = models.ImageField(storage=MyStorage())