Search code examples
pythondjangobotodjango-storage

Trying to understand Django source code and cause of missing argument TypeError


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)

Solution

  • 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())