djangodjango-modelsdjango-viewsdjango-formsdjango-extensions

How to use Django AutoSlugField output as ImageField filename


I am using django-extensions.AutoSlugField and django.db.models.ImageField.

To customize image name uploaded for django.db.models.ImageField, what I did:

from django.utils.text import slugify

# idea is to make image name the same as the automatically generated slug, however don't work
def update_image_name(instance, filename):
    # this debug instance, and instance.slug is empty string
    print(instance.__dict__)

    # I attempt to use slugify directly and see that it's not the same as the output generated by AutoSlugField
    # E.g. If I create display_name "shawn" 2nd time, AutoSlugField will return "shawn-2", but slugify(display_name) return "shawn"
    print(slugify(instance.display_name))

    return f"images/{instance.slug}.jpg"
 

class Object(models.Model):
    ...
    display_name = models.TextField()
    ...
    # to customize uploaded image name
    image = models.ImageField(blank=True, upload_to=update_image_name)
    ...
    # create slug automatically from display_name
    slug = AutoSlugField(blank=True, populate_from=["display_name"]

Based on what I debug, when I call instance inside update_image_name, slug is empty string.

If I understand correctly slug is only created at event save, so when I call ImageField instance, slug is not yet created, therefore empty string.

I think it might have something to do with event post save. However, I am not sure if that's the real reason or how to do that.

How can I get the automatically generated slug as my customized image name?


Solution

  • That's a tricky one because the order the fields are getting saved matters.

    The brute-force attack I'm suggesting would be to override the save method of your model and manually call the create_slug method before everything else ensuring the slug is set:

    from django.utils.encoding import force_str
    [...]
    
    class Object(models.Model):
        [...]
    
        def save(self, *args, **kwargs):
            self.slug = force_str(self._meta.get_field('slug').create_slug(self, False))
            super(Object, self).save(*args, **kwargs)
    

    That's what AutoSlugField does, refer to the code here. self._meta.get_field('slug') get's the slug field definition and then we just call the create_slug method.

    Tested under Python 3.7.9 & Django 3.1.5 like this:

    from django.core.files.uploadedfile import SimpleUploadedFile
    o = Object()
    o.display_name = "foo bar"
    o.image = SimpleUploadedFile(name='test_image.png', content=open('/path/to/test/image.png', 'rb').read(), content_type='image/png')
    o.save()
    

    Then I see update_image_name return images/foo-bar.jpg.