user18075594
user18075594

Reputation: 75

How to avoid creating image thumbnail every model.save() call?

I'm creating a news blog, so I decided to add a profile image. I was thinking, how to create image thumbnail better and decide to use percent resizing.

def save(self, *args, **kwargs):
    image = Image.open(self.image)  # Открываем картинку
    width, height = image.size  # Получаем размеры картинки
    new_image = BytesIO()  # Создаем байтовое представление

    resize = (width * (height // 10 * 5) // height, height // 10 * 5)  # Изменение по высоте

    if width > height:  # Если горизонтальная картинка
        resize = (width // 10 * 5, height * (width // 10 * 5) // width)  # Изменение по ширине

    image.thumbnail(resize, resample=Image.ANTIALIAS)  # Делаем миниатюру картинки
    image = image.convert('RGB')  # Убираем все лишние каналы
    image.save(new_image, format='JPEG', quality=90)  # Конвертируем в JPEG, ибо мало весит

    new_image.seek(0)  # Возвращение в начало файла

    name = f'{self.image.name.split(".")[0]}.jpeg'  # Имя файла

    # Перезапись файла в базе данных
    self.image = InMemoryUploadedFile(
        new_image, 'ImageField',  # Картинка, поля сохранения
        name, 'image/jpeg',  # Имя картинки, содержание
        getsizeof(new_image), None  # Размер, доп инфа
    )
    # Сохранение через другой save класса
    super(KvantUser, self).save(*args, **kwargs)

So, as you can see, I take only a half of the image size. It helps to keep good quality of the picture and reduce its size. But this method cause another problem.

Every time, when save() method called, image become smaller and smaller... I've tried to rewrite my change form, but it didn't help. As I knew, Django call save() method every time I change model, or login in system. So, I need help to solve this problem. Maybe it's better to create a fixed thumbnail, or you know a better way to do it. Feel free to suggest any ideas!

My model.py

def set_default_image():
    bucket = S3Boto3Storage()
    if not bucket.exists('default/user/user.png'):
        with open(join(settings.MEDIA_ROOT + '/default/user.png'), 'b+r') as f:
            bucket.save('default/user/user.png', f)
    return 'default/user/user.png'


def get_path(instance, filename):
    return f'user/{instance.username}/{filename}'


class KvantUser(AbstractUser):
    name = models.CharField(max_length=100)
    surname = models.CharField(max_length=100)
    patronymic = models.CharField(max_length=100)
    permission = models.CharField(choices=permission, max_length=100)
    color = models.CharField(max_length=100, choices=color, default='blue')
    theme = models.CharField(max_length=100, choices=theme, default='light')
    image = models.ImageField(upload_to=get_path, default=set_default_image)

My forms.py

class KvantUserCreationForm(UserCreationForm):
    class Meta(UserCreationForm):
        model = KvantUser
        fields = ('username', 'email', 'password', 'name', 'surname', 'patronymic', 'image')


class KvantUserChangeForm(UserChangeForm):
    class Meta:
        model = KvantUser
        fields = ('username', 'email', 'password', 'name', 'surname', 'patronymic', 'image')


class KvantUserLoginForm(forms.Form):
    username = forms.CharField(max_length=150)
    password = forms.CharField(max_length=150)

    def save(self, request):
        username = self.cleaned_data['username']  # Получение логина
        password = self.cleaned_data['password']  # Получение пароля

        if KvantUser.objects.filter(username=username).exists():  # Проверка существования акаунта
            user = authenticate(username=username, password=password)  # Попытка авторизации
            if user is not None:  # Если попытка удачна, то авторизуй и верни пользователя
                login(request, user)
                return user
        messages.error(request, 'Ошибка авторизации!')  # Сообщение об ощибки
        return None

P.S If you need another file, feel free to ask.

Upvotes: 2

Views: 91

Answers (1)

Abdul Aziz Barkat
Abdul Aziz Barkat

Reputation: 21807

If you only want to create the thumbnail when the KvantUser instance is created you can check if the pk exists before using your logic for resizing the image:

def save(self, *args, **kwargs):
    if self.pk is None:
        # Your code to make the thumbnail here
    super(KvantUser, self).save(*args, **kwargs)

But there is a better way, that is to put this logic in the form itself, since you have multiple forms for this model, write a mixin and inherit that in these forms:

class ImageThumbnailFormMixin:
    def clean_image(self):
        f = self.cleaned_data.get('image')
        if not f:
            return f
        image = f.image # A PIL Image object that Django has annotated here
        # Make the thumbnail here with the `image` object
        image.save()
        return f


class KvantUserCreationForm(ImageThumbnailFormMixin, UserCreationForm):
    class Meta(UserCreationForm):
        model = KvantUser
        fields = ('username', 'email', 'password', 'name', 'surname', 'patronymic', 'image')


class KvantUserChangeForm(ImageThumbnailFormMixin, UserChangeForm):
    class Meta:
        model = KvantUser
        fields = ('username', 'email', 'password', 'name', 'surname', 'patronymic', 'image')

Upvotes: 2

Related Questions