Search code examples
djangomodelimagefield

ImageField resizing on save also updating width_field and height_field


I have a model containing ImageField which should be resized after uploading.

class SomeModel(models.Model):
    banner = ImageField(upload_to='uploaded_images',
                        width_field='banner_width',
                        height_field='banner_height')
    banner_width = models.PositiveIntegerField(_('banner width'), editable=False)
    banner_height = models.PositiveIntegerField(_('banner height'), editable=False)

def save(self, *args, **kwargs):
    super(SomeModel, self).save(*args, **kwargs)
    resize_image(filename=self.banner.path,
                 width=MAX_BANNER_WIDTH,
                 height=MAX_BANNER_HEIGHT)

resize_image is a custom function which does the resizing, and everything works fine, except that banner_width and banner_height are populated with dimensions of original image, before resizing.

Actual size of resized image may be smaller than MAX values given, so I have to open resized file to check it's actual dimensions after resizing. I could then manually set banner_width and banner_height, and save again, but it's not efficient way. I could also do the resizing first, set width and height fields, and then save, but file at location self.banner.path doesn't exist before save is performed.

Any suggestions on how should this be done properly?


Solution

  • After several hours of trying to do this efficiently, I've changed my approach to this problem and defined CustomImageField like this:

    class CustomImageField(ImageField):
        attr_class = CustomImageFieldFile
    
        def __init__(self, resize=False, to_width=None, to_height=None, force=True, *args, **kwargs):
            self.resize = resize
            if resize:
                self.to_width = to_width
                self.to_height = to_height
                self.force = force
            super(CustomImageField, self).__init__(*args, **kwargs)
    
    
    class CustomImageFieldFile(ImageFieldFile): 
    
        def save(self, name, content, save=True):
            super(CustomImageFieldFile, self).save(name, content, save=save)
            if self.field.resize:
                resized_img = resize_image(filename=self.path,
                                           width=self.field.to_width,
                                           height=self.field.to_height,
                                           force=self.field.force)
                if resized_img:
                    setattr(self.instance, self.field.width_field, resized_img.size[0])
                    setattr(self.instance, self.field.height_field, resized_img.size[1])
    

    Now I can just define:

    class SomeModel(models.Model):
        my_image = CustomImageField(resize=True, to_width=SOME_WIDTH, to_height=SOME_HEIGHT, force=False,
                                    width_field='image_width', height_field='image_height')
        image_width = models.PositiveIntegerField(editable=False)
        image_height = models.PositiveIntegerField(editable=False)
    

    And depending on resize argument, image can be automatically resized after uploading, and width/height fields are correctly updated, without saving object twice. After quick tests it seems to be working fine.