afk
afk

Reputation: 558

generate thumbnail when image uploaded django

i have a website where multiple large size images uploaded everyday after they go live because of heavy size its taking time so i want to generate thumbnail version when image uploaded:

class Image(models.Model):
      license_type = (
    ('Royalty-Free','Royalty-Free'),
    ('Rights-Managed','Rights-Managed')
                         )
      image_number = models.CharField(default=random_image_number,max_length=12,unique=True)
      title = models.CharField(default=random_image_number,max_length = 100)
      image = models.ImageField(upload_to = 'image' , default = 'demo/demo.png')
      thumbnail = models.ImageField(upload_to='thumbs', editable=False)
      category = models.ForeignKey('Category', null=True, blank=True, on_delete=models.CASCADE)
      shoot = models.ForeignKey(ImageShoot, on_delete=models.CASCADE, related_name='Image', null=True,blank=True)
      image_keyword = models.TextField(max_length=1000)
      credit = models.CharField(max_length=150, null=True)
      location = models.CharField(max_length=100, null=True)
      license_type = models.CharField(max_length=20,choices=license_type, default='')
      uploaded_at = models.TimeField(auto_now_add=True)

      def __str__(self):
          return self.title

      def save(self, *args, **kwargs):

          super(Image, self).save(*args, **kwargs)
          if not self.make_thumbnail():
             raise Exception('Could not create thumbnail - is the file type valid?')

      def make_thumbnail(self):
          fh = storage.open(self.Image.name)
          try:
             image = PILImage.open(fh)
          except:
             return False
          image.thumbnail((400,400),PILImage.ANTIALIAS)
          fh.close()
          thumb_name, thumb_extension = os.path.splitext(self.Image.name)
          thumb_extension = thumb_extension.lower()

          thumb_filename = thumb_name + '_thumb' + thumb_extension

          if thumb_extension in ['.jpg', '.jpeg']:
             FTYPE = 'JPEG'
          elif thumb_extension == '.gif':
               FTYPE = 'GIF'
          elif thumb_extension == '.png':
               FTYPE = 'PNG'
          else:
             return False    # Unrecognized file type

    # Save thumbnail to in-memory file as StringIO
         temp_thumb = StringIO()
         image.save(temp_thumb, FTYPE)
         temp_thumb.seek(0)

    # Load a ContentFile into the thumbnail field so it gets saved
         self.thumbnail.save(thumb_filename, ContentFile(temp_thumb.read()), save=True)
         temp_thumb.close()

         return True

admin.py:

@admin.register(Image)
class ImageAdmin(admin.ModelAdmin):
      readonly_fields=['image_number','uploaded_at']
      fields = ['title','image_number','shoot','category',
         'image','image_keyword','credit','license_type','location','uploaded_at']

now this is the error:

string argument expected, got 'bytes'

backtrace:

File "/home/tboss/Desktop/environment/live/backend/venv/lib/python3.7/site-packages/PIL/JpegImagePlugin.py", line 779, in _save
ImageFile._save(im, fp, [("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize)
File "/home/tboss/Desktop/environment/live/backend/venv/lib/python3.7/site-packages/PIL/ImageFile.py", line 513, in _save
fp.write(d)
TypeError: string argument expected, got 'bytes'

when i user BytesIO:

i get this error:

maximum recursion depth exceeded in comparison

backtrace:

 File "/home/tboss/Desktop/environment/live/backend/venv/lib/python3.7/site-packages/PIL/TiffImagePlugin.py", line 319, in __init__
  if isinstance(value, Fraction):
 File "/home/tboss/Desktop/environment/live/backend/venv/lib/python3.7/abc.py", line 139, in __instancecheck__
 return _abc_instancecheck(cls, instance)

RecursionError: maximum recursion depth exceeded in comparison

i am current uploading image from admin but also going to using drf. currently its not creating thumbnail.............................................................................................................

Upvotes: 1

Views: 5411

Answers (2)

Mahadi Hassan
Mahadi Hassan

Reputation: 310

Best way is to use Django Advance Thumbnail. Here's a basic example of how to use the AdvanceDJThumbnailField in a model:

from django.db import models
from django_advance_thumbnail import AdvanceThumbnailField

class MyModel(models.Model):
    my_image = models.ImageField(upload_to='images/', null=True, blank=True)
    my_image_thumbnail = AdvanceThumbnailField(source_field='my_image', upload_to='images/thumbnails/', null=True, blank=True,
                                  size=(300, 300)) 

In this example, AdvanceDJThumbnailField is used to create a thumbnail from the image field. Whenever an image is uploaded or updated, a corresponding thumbnail is automatically generated and stored in the thumbnail field. The thumbnail's dimensions are determined by the optional size parameter, which defaults to (300, 300) if not specified.

This setup ensures that the lifecycle of the thumbnail is tied to its source image. If the source image is deleted, the associated thumbnail is also removed. This seamless synchronization simplifies image management in your Django models.

Installation of Django Advance Thumbnail.

pip install django_advance_thumbnail

Add django_advance_thumbnail to your INSTALLED_APPS in settings.py

INSTALLED_APPS = [
    # ...
    'django_advance_thumbnail',
    # ...
]

Upvotes: -1

George Bikas
George Bikas

Reputation: 1430

As said in the comment, you can work with base64 encoded image instead. In order to do this, first change the thumbnail field to the following:

thumbnail = models.CharField(max_length=2000, blank=True, null=True)

Then, override the model save method and add the following code:

def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
    if not self.image:
        self.thumbnail = None
    else:
        thumbnail_size = 50, 50
        data_img = BytesIO()
        tiny_img = Image.open(self.image)
        tiny_img.thumbnail(thumbnail_size)
        tiny_img.save(data_img, format="BMP")
        tiny_img.close()
        try:
            self.thumbnail = "data:image/jpg;base64,{}".format(
                base64.b64encode(data_img.getvalue()).decode("utf-8")
            )
        except UnicodeDecodeError:
            self.blurred_image = None

    super(Image, self).save(force_insert, force_update, using, update_fields)

You can change the thumbnail_size variable to match your needs. Lastly, add the following to your import section:

import base64
from io import BytesIO

Don't forget to run makemigrations and migrate commands! Let me know if you have any problems.

Upvotes: 3

Related Questions