I have a model where the thumbnail width varies between parent objects (ForeignKey). I need to be able to feed this info to imagekit processors. This is what I have:
class Wall(models.Model):
#...
width = models.SmallIntegerField(null=True, blank=True)
#...
class Poster(models.Model):
wall = models.ForeignKey(Wall, related_name='posters')
#...
original_image = models.ImageField(upload_to=upload_image_to)
def __init__(self, *args, **kwargs):
self.thumbnail = ImageSpecField([
Adjust(contrast=1.2, sharpness=1.1),
SmartResize(height=163, width=self.wall.width)
],
image_field='original_image', format='PNG'
)
super(Poster, self).__init__(*args, **kwargs)
#...
But if I do this, nothing happens, not even the thumbnail url is generated.
And the following will result this this exception:
AttributeError: 'ForeignKey' object has no attribute 'width'
class Poster(models.Model):
wall = models.ForeignKey(Wall, related_name='posters')
#...
original_image = models.ImageField(upload_to=upload_image_to)
thumbnail = ImageSpecField([
Adjust(contrast=1.2, sharpness=1.1),
SmartResize(height=163, width=wall.width)
],
image_field='original_image', format='PNG'
)
#...
You cannot reference instance values in the model definition. This is where it become a little tricky with Django; the Models are a declaration of what an instance will look like when instantiated which is why fields which are interdependent have to refer to other fields by name such as the image_field='original_image'
specification.
Looking from the source for imagekit, you can see that processors
can take either a list of static processors to apply, or it take a callable which should return the list of processors to apply at generation time. Since you want the generation to change at run time based on the width, you can use this to your advantage.
The processors
callable is called with the instance on which the thumbnail
field appears which then allows you to do the lookup of the width.
def thumbnail_processors(instance, file):
# Dynamic width lookup.
width = instance.wall.width
return [
Adjust(contrast=1.2, sharpness=1.1),
SmartResize(width=width, height=163),
]
class Poster(models.Model):
wall = models.ForeignKey(Wall, related_name='posters')
#...
original_image = models.ImageField(upload_to=upload_image_to)
thumbnail = ImageSpecField(
processors=thumbnail_processors,
image_field='original_image', format='PNG'
)
#...
Now when you access the field, thumbnail_processors
will be called to obtain the list of processors at run time rather than at Model declaration. The width
is obtained from your foreign key and the appropriate resizing performed.
There's probably some gotchas which will need investigation. When you access the thumbnail
an image file will be generated according to your width. If you later change the width and request the thumbnail again, I'm not sure how the storage and caching backend will behave. It's likely that you'll require a custom file name generator which can encode the width of the thumbnail so that when the width does change, a new thumbnail will be generated with a different name.