Is there a way to use sorl-thumbnail url without generating the thumbnail?

808 views Asked by At

I want to generate a thumnail list. I opted for sorl-thumbnail because it seems largely used and developped.

My problem is with the template tag "thumbnail": it generates the thumbnail image and the thumbnail object correctly but I only want the url.

I don't mind if the thumbnail image is not already generated/ready, only the template generation concerns me. I can serve my thumbnails through a custom view, this is not a problem as those will only be available to few people.

So to simplify I want to de-couple the url generation from the thumbnail generation itself.

Is it possible with sorl-thumbnail? if not, do you know another project that can?

I prefer not writing my own if something can already do it.

P.S. I will store thumbnails on disk.

1

There are 1 answers

1
Wtower On

I faced exactly the same issue early in my Django experience. I had a requirement that all generated thumbnails are stored on disk. We also had an issue on the deployment server with PIL (back then several problems with Python 3).

We couldn't find anything to help us, so we ended up with the following. We also ended up being imagemagick from command line to avoid PIL.

The idea was to create a custom tag asking for a thumbnailed image. If that would not exist, the tag would create the thumbnail and store it on disk for later use (in a subdir with the name of the style). Therefore there is a delay only the first time someone visits the page. This idea comes from Drupal.

Also, the thumbnail dimensions are specific so we hard-coded them on the project settings, but it shouldn't be too difficult to change this.

Frankly, I am not sure if this is the best solution, but it still works. Also it was one of my early codes in Python so be gentle with it (right now I am fully aware about eg. os.join etc, but no time to improve and test yet; but I am also very interested in your constructive opinion).

First, here are the settings:

IMAGE_STYLES = {
    'thumbnail_style': {
        'type': 'thumbnail',  # maintain a max of one of the two dimensions, depending on aspect ratio
        'size': (150, 1000)
    },
    'thumbnail_crop_style': {
        'type': 'thumbnail-crop',  # maintain both dimensions by cropping the remaining
        'size': (150, 150)
    },
    'thumbnail_upscale_style': {
        'type': 'thumbnail-upscale',  # same as first, but allow upscaling
        'size': (150, 150)
    },
}

This is the custom tag:

from django import template
from django.template.defaultfilters import stringfilter
from django.conf import settings
from subprocess import check_output, call
import os


register = template.Library()


@register.filter
@stringfilter
def image_style(url, style):
    """ Return the url of different image style
    Construct appropriately if not exist

    Assumptions:
    - Works only for Linux OS; double slashes are anyway ignored
    - MEDIA_URL is local (url is in form MEDIA_URL.. (eg /media/..)

    :param url: An image url
    :param style: Specify style to return image
    :return: image url of specified style
    """
    path_file_name = settings.BASE_DIR + url
    style_url_path = '/'.join((os.path.dirname(url), style))
    style_url = '/'.join((style_url_path, os.path.basename(url)))
    style_path = settings.BASE_DIR + style_url_path
    style_path_file_name = settings.BASE_DIR + style_url
    style_def = settings.IMAGE_STYLES[style]

    if not os.path.exists(style_path_file_name):
        if not os.path.exists(style_path):
            os.makedirs(style_path)
        by = chr(120)   # x
        plus = chr(43)  # +
        source_size_str = str(check_output(['identify', path_file_name])).split(' ')[2]
        source_size_array = source_size_str.split(by)
        source_size_x = int(source_size_array[0])
        source_size_y = int(source_size_array[1])
        target_size_x = style_def['size'][0]
        target_size_y = style_def['size'][1]
        target_size_str = str(target_size_x) + by + str(target_size_y)
        if style_def['type'] == 'thumbnail':
            if target_size_x > source_size_x and target_size_y > source_size_y:
                target_size_str = source_size_str
            call(['convert', path_file_name, '-thumbnail', target_size_str, '-antialias', style_path_file_name])
        elif style_def['type'] == 'thumbnail-upscale':
            call(['convert', path_file_name, '-thumbnail', target_size_str, '-antialias', style_path_file_name])
        elif style_def['type'] == 'thumbnail-crop':
            source_ratio = float(source_size_x) / float(source_size_y)
            target_ratio = float(target_size_x) / float(target_size_y)
            if source_ratio > target_ratio:  # crop vertically
                crop_target_size_x = int(source_size_y * target_ratio)
                crop_target_size_y = source_size_y
                offset = (source_size_x - crop_target_size_x) / 2
                crop_size_str = str(crop_target_size_x) + by + str(crop_target_size_y) + plus + str(offset) + plus + '0'
            else:  # crop horizontally
                crop_target_size_x = source_size_x
                crop_target_size_y = int(source_size_x / target_ratio)
                offset = (source_size_y - crop_target_size_y) / 2
                crop_size_str = str(crop_target_size_x) + by + str(crop_target_size_y) + plus + '0' + plus + str(offset)
            call(['convert', path_file_name, '-crop', crop_size_str, style_path_file_name])
            call(['convert', style_path_file_name, '-thumbnail', target_size_str, '-antialias', style_path_file_name])
    return style_url

This is how to use in ntemplate:

<img src="{{ image_field.url|image_style:'my_style' }}">