Django更新记录时用PIL缩放图片遇ValueError: seek of closed file
Django更新Player模型时触发"seek of closed file"错误解决方法
问题背景
定义的Players模型包含图片字段,save方法会调用图片缩放函数,但更新操作时无论是否上传新图片,都会触发以下错误:
ValueError: seek of closed file
相关代码如下:
Players模型
class Players(models.Model): team = models.ForeignKey(Teams, verbose_name=_('Team'), on_delete=models.CASCADE) player_name = models.CharField(_('Player Name'),max_length=200) player_description = models.TextField(_('Player Description')) player_image = models.ImageField(_('Profile Pic'),upload_to='upload/player_image', null=True, blank=True) player_social = models.CharField(_('Social Media Tag'),max_length=200) class Meta: verbose_name = _("Players") verbose_name_plural = _("Players") def __str__(self): return self.team.team_name + ' - ' + self.player_name def save(self, *args, **kwargs): super().save(*args, **kwargs) if self.player_image: image_resize(self.player_image, 250)
图片缩放函数
# Use PIL to resize images; set target width on each def image_resize(image, tgt_width): img = PIL.Image.open(image) img.load() width, height = img.size target_width = tgt_width h_coefficient = width/tgt_width target_height = height/h_coefficient img = img.resize((int(target_width), int(target_height)), PIL.Image.ANTIALIAS) img.save(image.path, quality=100) img.close() image.close()
更新视图
@method_decorator(superuser_required, name='dispatch') class PlayerUpdateView(UpdateView): model = Players template_name = 'scoreboard/players/player_update.html' form_class = PlayersForm context_object_name: str = 'player' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) team_id = Players.objects.get(pk=self.kwargs['pk']).team.id context["team"] = Teams.objects.get(pk=team_id) return context def form_valid(self, form): form.save() return super().form_valid(form) def get_success_url(self): team_id = Players.objects.get(pk=self.kwargs['pk']).team.id return reverse_lazy('team_detail', kwargs={'pk': team_id})
错误原因
- 调用
super().save()后,Django会自动关闭上传文件的文件句柄,此时再通过self.player_image这个FieldFile对象打开文件,会触发句柄已关闭的错误。 - 即使未上传新图片,直接操作FieldFile对象也可能因内部状态问题导致报错。
- 手动调用
image.close()干扰了Django对文件句柄的管理。
解决方案
1. 修改模型save方法,仅处理新上传的图片并使用文件路径
调整save方法逻辑,仅在有新图片上传时执行缩放,且通过文件路径而非FieldFile对象操作:
def save(self, *args, **kwargs): # 标记是否有新上传的图片:存在图片且未提交到数据库 need_resize = self.player_image and not self.player_image._committed # 先保存模型到数据库 super().save(*args, **kwargs) # 仅当有新图片时执行缩放,用文件路径打开避免句柄问题 if need_resize: image_resize(self.player_image.path, 250)
2. 重写图片缩放函数,直接操作文件路径
更新缩放函数,接收文件路径作为参数,使用with语句自动管理图片资源:
from PIL import Image def image_resize(image_path, tgt_width): with Image.open(image_path) as img: width, height = img.size target_width = tgt_width h_coefficient = width / tgt_width target_height = height / h_coefficient # 用LANCZOS替代已弃用的ANTIALIAS img = img.resize((int(target_width), int(target_height)), Image.LANCZOS) img.save(image_path, quality=100)
3. 优化视图中的重复数据库查询
避免重复查询数据库,直接从已获取的self.object中获取关联数据:
@method_decorator(superuser_required, name='dispatch') class PlayerUpdateView(UpdateView): model = Players template_name = 'scoreboard/players/player_update.html' form_class = PlayersForm context_object_name: str = 'player' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # 直接从当前对象取team,无需重复查询 context["team"] = self.object.team return context def get_success_url(self): # 直接使用当前对象的team id return reverse_lazy('team_detail', kwargs={'pk': self.object.team.id})
内容的提问来源于stack exchange,提问作者Victor Donoso
相关产品推荐
相关产品推荐

