Django Form 用PIL来clean ImageField

最近在学习使用Django搭建网站,在用户个人资料界面,想要上传用户头像,并把所上传的图像重设大小,再保存到用户的头像属性中。遇到了一些问题,花了一晚上时间终于解决了。

 


 

主要是针对forms.py中的这个更新头像的表单类,我这里的名字是 ProfileHeadUpdateForm

class ProfileHeadUpdateForm(forms.ModelForm):

我在models.py中定义了头像字段名为 head_image

head_image = models.ImageField(verbose_name='头像', upload_to='head', default="head/default.png")

在forms.py的class ProfileHeadUpdateForm中,定义了 clean_head_image 函数:

def clean_head_image(self):

先获取用户所上传的图片:

image = self.cleaned_data.get("head_image")

如果用户选择了图片,这里image是个InMemoryUploadedFile类(只要图片大小小于2.5M);如果没有选择图片,image是个str,就是default对应的图片的路径。所以这里判断一下用户有没有选择图片:

if type(image) == str:
  raise forms.ValidationError("请选择一张图片")

之后就处理这个InMemoryUploadedFile类,它就是那个图片,只不过它存在于服务器内存中,而不是服务器硬盘上。但是PIL的Image.open的参数是(fp, mode="r"),PIL中对其解释如下:

"""
:param fp: A filename (string), pathlib.Path object or a file object.   The file object must implement :py:meth:`~file.read`,
"""

也就是说,fp可以是个字符串,即文件路径,从硬盘中读取数据,也可以是个file对象。而InMemoryUploadedFile的父类实现了跟file一样的方法(它的所有父类全部都在django中定义,所以它本质上应该不是个file object,但是如果实现了与file object一样的方法,之后在PIL中使用它应该是可以的,因为python不管你是不是file object还是别的,它只在意你有没有这个方法。如果我没理解错的话)。因此fp参数也可以是这个InMemoryUploadedFile类的实例。下面用PIL读取image,并进行重设大小:

from PIL import Image
img = Image.open(image)
img = img.resize((200, 200), Image.ANTIALIAS)

之后就遇到了最重要的问题。因为forms中的clean方法必须返回跟image类型一样的实例,问题就在于我虽然把image的图像改了,现在它是PIL的某个类的实例,我如何才能把img转化成InMemoryUploadedFile类的实例呢?

最终我决定重新生成一个InMemoryUploadedFile类的实例。它需要的参数是:

def __init__(self, file, field_name, name, content_type, size, charset, content_type_extra=None):

其中field_name, name, content_type, charset, content_type_extra 可以通过image的属性获取,但是file和size就有点问题了。现在问题变成,如何用PIL将自己的图片实例转化为file object,并获取其size。

看了一下PIL的save方法:

def save(self, fp, format=None, **params):
"""
:param fp: A filename (string), pathlib.Path object or file object.
:param format: Optional format override. If omitted, the
  format to use is determined from the filename extension.
  If a file object was used instead of a filename, this
  parameter should always be used.
"""

PIL可以把图片保存在file object(即参数fp所指的file object)中,也可以保存在硬盘上。这取决与fp的类型。但是如果要保存在file object中,format参数是必须给定的。但是format从哪里获得呢?从image的content_type属性就可以获得。先在内存生成一个file object(因为图片是二进制文件,所以不能用StringIO):

from io import BytesIO
img_io = BytesIO()

再用PIL把img保存到这个对象里:

img.save(img_io, format=image.content_type.split('/')[-1].upper(), quality='keep')

最后只差size了,看了一下BytesIO并没有提供方法来获取这个图像的size。查了一些方法,最终还是用了最简单的sys.getsizeof()方法:

from sys import getsizeof
size = getsizeof(img_io)

最后生成一个InMemoryUploadedFile类的实例并返回它:

image = InMemoryUploadedFile(img_io, image.field_name, image.name, image.content_type, size, image.charset, image.content_type_extra)
return image

 

总结一下代码:

from io import BytesIO
from sys import getsizeof
from django import forms
from django.contrib.auth.forms import UserCreationForm, UsernameField
from .models import User
from PIL import Image
from django.core.files.uploadedfile import InMemoryUploadedFile

class ProfileHeadUpdateForm(forms.ModelForm):
  class Meta:
  model = User
  fields = ("head_image",)

  def clean_head_image(self):
    image = self.cleaned_data.get("head_image")
    if type(image) == str:
      raise forms.ValidationError("请选择一张图片")
    img = Image.open(image).resize((200, 200), Image.ANTIALIAS)
    img_io = BytesIO()
    img.save(img_io, format=image.content_type.split('/')[-1].upper(), quality='keep')
    image = InMemoryUploadedFile(img_io, image.field_name, image.name, image.content_type, getsizeof(img_io), image.charset, image.content_type_extra)
    return image


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hull Qin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值