我搜索了一种在保存之前调整上传照片大小的解决方案。这里和那里(在 StackOverflow 中)有很多信息。然而,没有完整的解决方案。这是我认为适合需要它的人的最终解决方案。
发展亮点
- 使用 Pillow 进行图像处理(需要两个包:libjpeg-dev、zlib1g-dev)
- 使用 Model 和 ImageField 作为存储
- 将 HTTP POST 或 PUT 与 multipart/form 一起使用
- 无需手动将文件保存到磁盘。
- 创建多个分辨率并存储它们的尺寸。
- 没有修改模型本身
安装枕头
$ sudo apt-get install libjpeg-dev
$ sudo apt-get install zlib1g-dev
$ pip install -I Pillow
myapp/models.py
from django.db import models
class Post(models.Model):
caption = models.CharField(max_length=100, default=None, blank=True)
image_w = models.PositiveIntegerField(default=0)
image_h = models.PositiveIntegerField(default=0)
image = models.ImageField(upload_to='images/%Y/%m/%d/', default=None,
blank=True, width_field='image_w', height_field='image_h')
thumbnail = models.ImageField(upload_to='images/%Y/%m/%d/', default=None,
blank=True)
此模型保存带有缩略图和可选标题的照片。这应该类似于现实世界的用例。
我们的目标是将照片调整为 640x640 并生成 150x150 的缩略图。我们还需要在 Web API 中返回照片的尺寸。 image_w 和 image_h 是表中的缓存维度。请注意,我们需要在声明 ImageField 时添加配额 '。但是,thumbnail 不需要宽度和高度字段(因此您可以看到不同)。
这就是模型。我们不需要重写或向模型添加任何函数。
myapp/serializers.py
我们将使用ModelSerializer 来处理来自 HTTP POST 的传入数据。
from rest_framework import serializers
from myapp.models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('caption',)
我们不希望序列化程序处理上传的照片 - 我们会自己处理。因此,无需在字段中包含“图像”。
myapp/views.py
@api_view(['POST'])
@parser_classes((MultiPartParser,))
def handle_uploaded_image(request):
# process images first. if error, quit.
if not 'uploaded_media' in request.FILES:
return Response({'msg': 'Photo missing.'}, status.HTTP_400_BAD_REQUEST)
try:
im = Image.open(StringIO(request.FILES['uploaded_media'].read()))
except IOError:
return Response({'msg': 'Bad image.'}, status.HTTP_400_BAD_REQUEST)
serializer = PostSerializer(data=request.DATA, files=request.FILES)
if not serializer.is_valid():
return Response({'msg': serializer.errors}, status.HTTP_400_BAD_REQUEST)
post = Post.create()
if serializer.data['caption'] is not None:
post.caption = serializer.data['caption']
filename = uuid.uuid4()
name = '%s_0.jpg' % (filename)
post.image.save(name=name, content=resize_image(im, 640))
name = '%s_1.jpg' % (filename)
post.thumbnail.save(name=name, content=resize_image(im, 150))
post.save()
return Response({'msg': 'success',
'caption': post.caption,
'image': {
'url': request.build_absolute_uri(post.image.url),
'width': post.image_w,
'height': post.image_h,
}
'thumbnail': request.build_absolute_uri(post.thumbnail.url),
}, status.HTTP_201_CREATED)
共享 py 中的辅助函数
def resize_image(im, edge):
(width, height) = im.size
(width, height) = scale_dimension(w, h, long_edge=edge)
content = StringIO()
im.resize((width, height), Image.ANTIALIAS).save(fp=content, format='JPEG', dpi=[72, 72])
return ContentFile(content.getvalue())
def scale_dimension(width, height, long_edge):
if width > height:
ratio = long_edge * 1. / width
else:
ratio = long_edge * 1. / height
return int(width * ratio), int(height * ratio)
在这里,我们不使用 Image.thumbnail,因为它会改变原始图像。如果我们要存储多个分辨率(例如用于不同目的的低分辨率和高分辨率),则此代码适用。我发现两次调整传入消息的大小会降低质量。
我们也不将图像直接保存到磁盘。我们通过 ContentFile(内存中的内容的 File 对象)将图像推送到 ImageField 并让 ImageField 完成它的工作。我们希望图像的多个副本具有相同的文件名和不同的后缀。
学分
以下是我在构建此解决方案时参考的代码链接:
如果您还想验证图像,请参阅:https://stackoverflow.com/a/20762344/3731039