You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Django表单中处理图片:压缩至300KB并裁剪为1280×820px

Django上传图片压缩与裁剪问题解决

需求:将上传图片压缩至300KB以下,同时裁剪为1280px×820px固定尺寸,确保规格统一。以下是原尝试代码(未生效):

ALLOWED_EXTENSIONS = ('.gif', '.jpg', '.jpeg')

class PropertyForm(forms.ModelForm): 
    description = forms.CharField(label='Property Description:', max_length=60, widget=forms.TextInput(attrs={'placeholder': 'Briefly Describe your Property. E.g. Bedroom & Palour with Private Detached Bathroom'}))
    state = forms.ChoiceField(choices=Property.STATE_CHOICES, required=False)
    state_lga = forms.CharField(label = 'Local Govt. Area:', max_length=12, widget=forms.TextInput(attrs={'placeholder': 'Enter Local Govt. of Property.'}))
    address = forms.CharField(label = 'Property Address:', max_length=60, widget=forms.TextInput(attrs={'placeholder': 'Enter Street Name with Number and Town Name only.'}))
class Meta:
    model = Property
    fields = ('description', 'address', 'country', 'state', 'state_lga', 'property_type', 'bedrooms', 'bathroom_type', 'price', 'is_available', 'image')

def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.fields['bedrooms'].required = False


def clean_image(self):
    image = self.cleaned_data.get('image')

if image:
    # Check if the image size exceeds 1MB
    if image.size > 1024 * 1024:  # 1MB in bytes
        # Open the image using Pillow
        with Image.open(image) as img:
            # Reduce the image size while preserving the aspect ratio
            max_width = 1920
            max_height = 820
            img.thumbnail((max_width, max_height), Image.ANTIALIAS)

            # Save the modified image with reduced quality to achieve a smaller file size
            buffer = BytesIO()
            img.save(buffer, format='JPEG', optimize=True, quality=85)
            while buffer.tell() > 1024 * 1024:  # Check if file size exceeds 1MB
                quality -= 5
                if quality < 5:
                    break
                buffer.seek(0)
                buffer.truncate(0)
                img.save(buffer, format='JPEG', optimize=True, quality=quality)

            buffer.seek(0)
            image.file = buffer

    # Check file extension for allowed formats
    allowed_formats = ['gif', 'jpeg', 'jpg']
    ext = image.name.split('.')[-1].lower()
    if ext not in allowed_formats:
        raise forms.ValidationError("Only GIF, JPEG, and JPG images are allowed.")
    
    
else:
    raise forms.ValidationError("Please upload an image before submitting the form.")

return image

问题分析

  1. 缩进错误:clean_image方法内的if image:代码块未缩进,导致逻辑不在方法内部执行,完全失效。
  2. 尺寸处理错误:原代码用thumbnail按比例缩放至1920×820,而非需求的固定裁剪1280×820,无法保证尺寸统一。
  3. 压缩阈值不符:原代码判断1MB,需求是300KB(即300 * 1024字节)。
  4. 变量未初始化:循环中直接操作quality变量,但未提前定义初始值,会引发报错。
  5. 裁剪逻辑缺失:没有实现按目标尺寸裁剪的逻辑,仅做了缩放。

修正后的代码

from PIL import Image
from io import BytesIO
from django import forms
from .models import Property  # 确保导入你的模型

ALLOWED_EXTENSIONS = ('.gif', '.jpg', '.jpeg')

class PropertyForm(forms.ModelForm): 
    description = forms.CharField(
        label='Property Description:', 
        max_length=60, 
        widget=forms.TextInput(attrs={
            'placeholder': 'Briefly Describe your Property. E.g. Bedroom & Palour with Private Detached Bathroom'
        })
    )
    state = forms.ChoiceField(choices=Property.STATE_CHOICES, required=False)
    state_lga = forms.CharField(
        label='Local Govt. Area:', 
        max_length=12, 
        widget=forms.TextInput(attrs={'placeholder': 'Enter Local Govt. of Property.'})
    )
    address = forms.CharField(
        label='Property Address:', 
        max_length=60, 
        widget=forms.TextInput(attrs={'placeholder': 'Enter Street Name with Number and Town Name only.'})
    )

    class Meta:
        model = Property
        fields = ('description', 'address', 'country', 'state', 'state_lga', 'property_type', 'bedrooms', 'bathroom_type', 'price', 'is_available', 'image')

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['bedrooms'].required = False

    def clean_image(self):
        image = self.cleaned_data.get('image')

        if not image:
            raise forms.ValidationError("Please upload an image before submitting the form.")

        # 检查文件格式
        allowed_formats = ['gif', 'jpeg', 'jpg']
        ext = image.name.split('.')[-1].lower()
        if ext not in allowed_formats:
            raise forms.ValidationError("Only GIF, JPEG, and JPG images are allowed.")

        # 打开图片处理
        with Image.open(image) as img:
            # 转换为RGB(处理GIF等带透明通道的格式,避免保存JPEG报错)
            if img.mode in ('RGBA', 'P'):
                img = img.convert('RGB')

            target_width, target_height = 1280, 820
            img_width, img_height = img.size

            # 计算裁剪区域:中心裁剪,匹配目标比例
            target_ratio = target_width / target_height
            img_ratio = img_width / img_height

            if img_ratio > target_ratio:
                # 图片太宽,裁剪左右两侧
                new_width = int(img_height * target_ratio)
                left = (img_width - new_width) // 2
                right = left + new_width
                top, bottom = 0, img_height
            else:
                # 图片太高,裁剪上下两侧
                new_height = int(img_width / target_ratio)
                top = (img_height - new_height) // 2
                bottom = top + new_height
                left, right = 0, img_width

            # 执行裁剪
            img = img.crop((left, top, right, bottom))
            # 缩放至目标尺寸(确保完全匹配)
            img = img.resize((target_width, target_height), Image.Resampling.LANCZOS)

            # 压缩至300KB以下
            buffer = BytesIO()
            quality = 85
            max_size = 300 * 1024  # 300KB字节数

            while True:
                buffer.seek(0)
                buffer.truncate(0)
                img.save(buffer, format='JPEG', optimize=True, quality=quality)
                if buffer.tell() <= max_size or quality <= 5:
                    break
                quality -= 5

            buffer.seek(0)
            # 替换原图片文件对象
            image.file = buffer
            # 更新文件名后缀为jpeg(统一格式)
            image.name = f"{image.name.rsplit('.', 1)[0]}.jpeg"

        return image

关键修正点说明

  • 修复缩进:所有图片处理逻辑放入clean_image方法内部。
  • 中心裁剪:先按目标比例裁剪图片,再缩放至固定尺寸,保证所有图片规格统一。
  • 调整压缩阈值:将目标文件大小改为300KB。
  • 初始化变量:提前定义quality初始值,避免报错。
  • 格式兼容:处理带透明通道的图片(如GIF),转换为RGB后保存为JPEG,避免格式冲突。
  • 循环压缩:逐步降低质量直到文件大小符合要求,最低质量设为5防止无限循环。

内容的提问来源于stack exchange,提问作者apollos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 02:14:57