如何在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
问题分析
- 缩进错误:
clean_image方法内的if image:代码块未缩进,导致逻辑不在方法内部执行,完全失效。 - 尺寸处理错误:原代码用
thumbnail按比例缩放至1920×820,而非需求的固定裁剪1280×820,无法保证尺寸统一。 - 压缩阈值不符:原代码判断1MB,需求是300KB(即
300 * 1024字节)。 - 变量未初始化:循环中直接操作
quality变量,但未提前定义初始值,会引发报错。 - 裁剪逻辑缺失:没有实现按目标尺寸裁剪的逻辑,仅做了缩放。
修正后的代码
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
相关产品推荐
相关产品推荐

