1

I,m working with my first project in Django, And I have a model like that:

def get_image_path(instance, filename):
    category_name_path = instance.category.category_name
    return f"{category_name_path}/{filename}"


class Gallery(models.Model):
    description = models.TextField()
    category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
    image = ResizedImageField(size=[1920, 1920], upload_to=get_image_path)
    
    def __str__(self):
            return '%s %s %s' % (self.category, self.image.url, self.description)

It works but I cannot understand how the 'filename' argument is passed to the function? How it works? ;)

Ivan Starostin
  • 8,798
  • 5
  • 21
  • 39
Kamil
  • 15
  • 4
  • Could you clarify what you're confused about? `filename` is just the second parameter. Are you asking when/where `get_image_path` gets called? – Brian61354270 Apr 20 '23 at 18:25
  • yes, I'm confused why in this fragment: "upload_to=get_image_path" there is nothing paased to the function, I've expected something like that: "upload_to=get_image_path(x, y)" – Kamil Apr 20 '23 at 18:29
  • 1
    That object will call the function later, potentially multiple times with different inputs. [You can read the documentation here](https://docs.djangoproject.com/en/4.2/ref/models/fields/#django.db.models.FileField.upload_to). You should also look up the term "first-class functions", which is what this paradigm is called where a function can be used as a value. – Patrick Haugh Apr 20 '23 at 18:33

1 Answers1

0

The FileField, which is a superclass of the ImageField has a method .generate_filename(…) [GitHub]:

def generate_filename(self, instance, filename):
    if callable(self.upload_to):
        filename = self.upload_to(instance, filename)
    else:
        # …
        pass
    filename = validate_file_name(filename, allow_relative_path=True)
    return self.storage.generate_filename(filename)

This will thus call the parameter. We can also look where the call to .generate_filename originates from:

This is by the .save(…) method [GitHub] of the FieldFile (not to be confused with FileField, FieldFile is the file stored in the field):

def save(self, name, content, save=True):
    name = self.field.generate_filename(self.instance, name)
    self.name = self.storage.save(name, content, max_length=self.field.max_length)
    setattr(self.instance, self.field.attname, self.name)
    self._committed = True

    # Save the object because it has changed, unless save is False
    if save:
        self.instance.save()

then this simply originates from the FileField's .pre_save(…) method [GitHub] that calls .save() on the FileField:

def pre_save(self, model_instance, add):
    file = super().pre_save(model_instance, add)
    if file and not file._committed:
        # Commit the file to storage prior to saving the model
        file.save(file.name, file.file, save=False)
    return file

and it thus picks the .name of the FieldFile.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555