2
votes

I have a model for Photo, with two image fields. One is for the original image and the other is for the resized version of the original image.

class Photo(models.Model):
    user = models.ForeignKey(User)
    image_original = models.ImageField(
        upload_to=get_upload_file_name,
        width_field="image_original_width",
        height_field="image_original_height",
        blank=True
    )
    image_original_width = models.IntegerField(default=0)
    image_original_height = models.IntegerField(default=0)
    image_470 = models.ImageField(
        upload_to=get_upload_file_name,
        width_field="image_470_width",
        height_field="image_470_height",
        blank=True
    )
    image_470_width = models.IntegerField(default=0)
    image_470_height = models.IntegerField(default=0)

The reason I chose django-channles was because I was already using it for the websocket purpose, and in the docs they said "... And, beyond that, there are plenty of non-critical tasks that applications could easily offload until after a response has been sent - like saving things into a cache or thumbnailing newly-uploaded images.".

How can I use django-channels to resize the image to have width of 470px and auto height using sorl-thumbnail or django-imagekit or in any other way?

1
I don't see how channels how anything to do with resizing images. Could you elaborate why do you want to use django-channels to do this task and what do you mean by that? Also, are you dead set on sorl-thumbnail? The model of having separate model fields for different versions of the same image seems more up django-imagekit's alley.Ludwik Trammer
@LudwikTrammer django-imagekit looks promising. Thank you. I would definitely give this a try. And the reason I chose django-channles was because I was already using it for the websocket purpose, and in the docs they said "And, beyond that, there are plenty of non-critical tasks that applications could easily offload until after a response has been sent - like saving things into a cache or thumbnailing newly-uploaded images.".Karl
Thank you for the response. I'd suggest adding this information to your original question.Ludwik Trammer
@LudwikTrammer Ok. Thank you.Karl

1 Answers

0
votes

If you want to use sorl-thumbnail, then you don't need to do anything in your model code. Just have a class like this:

class Photo(models.Model):
    user = models.ForeignKey(User)
    image = models.ImageField(
        upload_to=get_upload_file_name,
        width_field="width",
        height_field="height",
        blank=True
    )
    width = models.IntegerField(default=0)
    height = models.IntegerField(default=0)

And whenever you need a 470px image, do like this in your template:

{% load thumbnail %}
...

{# Specifying width only here. #}
{# If you want a height constraint as well use e.g. "470x1000". #}
{% thumbnail photo.image "470" as im %}
    <img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
{% endthumbnail %}

That's it — sorl-thumbnail will take care of the rest (scaling on first request, handling the cache, etc). It operates on-demand, i.e. the resizing will be done on the first access (when the template tag is encountered) - which is normally fine. The only thing you should keep in mind is that sorl-thumbnail won't work well if your storage is slow. If you store media on a local drive - that's fine. If your storage backend is, say, Amazon S3 - don't do it.

The convenient part of this is shall you need other thumbnail sizes, you just tell sorl-thumnail you need them, right in your template code. No model changes necessary.

If you want to ensure the thumbnails are pre-generated with sorl-thumbnail, you can either call get_thumbnail explicitly or use, e.g. a Celery task that would run get_thumbnail (ignoring the result) for you, e.g.

@app.task
def ensure_thumbnail(photo_pk, size="470"):
    photo = Photo.objects.get(pk=photo_pk)
    get_thumbnail(photo.image, size)

And whenever you see a new upload, call ensure_thumbnail.delay(photo.pk).

Be sure to check out the examples for more information.


If you feel this this approach isn't how you want things to be, or you have a slow storage then use django-imagekit. I'm not much familiar with it (haven't used in production myself), but based on the documentation it looks like this:

Your model would look like:

from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFit
...

class Photo(models.Model):
    user = models.ForeignKey(User)
    image_original = models.ImageField(
        upload_to=get_upload_file_name,
        width_field="image_original_width",
        height_field="image_original_height",
        blank=True
    )
    image_original_width = models.IntegerField(default=0)
    image_original_height = models.IntegerField(default=0)
    # I haven't found a way to not specify the height.
    image_470 = ImageSpecField(source="image_original",
                               processors=[ResizeToFit(470, 1000)],
                               format="JPEG")

(Like sorl-thumbnail, this won't actually create a database field. If you absolutely want a fully managed independent image file, with its own database field - seems that your best option is to explicitly generate thumbnails in Photo.save using a bare Pillow library.)

Then it's just plain and simple:

<img src="{{ photo.image_470.url }}"
     width="{{ photo.image_470.width }}"
     height="{{ photo.image_470.height }}"
     alt="..." />

Be sure to check out documentation on caching which explains how the caching works (and to generate thumnails asynchronously, shall you want it this way).