使用Django的FileField跟踪图像类型
我的模型里有一个FileField
,可以接收图片。我用Pillow来验证这些图片,并确保它们是正确的格式。不过,我还想把图片文件保存为合适的扩展名。比如说,如果我上传一个名叫foo.gif
的JPEG图片,我希望把它保存为000.jpg
,这里的数字是模型里的另一个字段。
我应该把图片格式存在哪里,这样我就可以在upload_to
这个可调用的地方再用到它,来生成上传路径呢?
class ImageField(models.FileField):
default_error_messages = {
'invalid_image': "Upload a valid image. The file you uploaded was either not an image or a corrupted image.",
'invalid_format': "Upload an image in a valid format. The supported formats are JPEG and PNG.",
}
def to_python(self, data):
f = super(ImageField, self).to_python(data)
if f is None:
return None
if hasattr(data, 'temporary_file_path'):
file = data.temporary_file_path()
elif hasattr(data, 'read'):
file = BytesIO(data.read())
else:
file = BytesIO(data['content'])
try:
image = Image.open(file)
image.verify()
if image.format not in {'PNG', 'JPEG'}:
raise ValidationError(self.error_messages['invalid_format'], code='invalid_format')
except Exception:
raise ValidationError(self.error_messages['invalid_image'], code='invalid_image')
if hasattr(f, 'seek') and callable(f.seek):
f.seek(0)
return f
class Image(models.Model):
number = models.PositiveSmallIntegerField()
image = ImageField(upload_to=self.upload_path)
def upload_path(self, instance, upload_filename):
return ???
3 个回答
哦,我明白了。抱歉之前没理解清楚。
Pillow提供了一个叫做Image Class的类,这个类里面包含了关于图片的信息。你可能想要的属性是Image.format
。
我自己还做了个小测试,看看文件扩展名是否重要:
user@host:~/Images$ cp myimage.jpg myimage.gif
user@host:~/Images$ cp myimage.jpg myimage.txt # Extreme test!
user@host:~/Images$ python
Python 2.7.4 (default, Apr 19 2013, 18:28:01)
[GCC 4.7.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from PIL import Image
>>> im = Image.open('/home/user/Images/myimage.jpg')
>>> im.format
'JPEG'
>>> im = Image.open('/home/user/Images/myimage.gif')
>>> im.format
'JPEG'
结果是,不重要。看起来很好! ☺
其实,从我能看出来的,你似乎根本不需要用到Pillow库(除非你的项目还有其他没提到的部分)。你只需要定义一个存储系统,然后在取用的时候,访问一下FileField
的FieldFile
类。我推荐这种方法,因为99%的情况下,文件的信息和格式都是通过使用文件对象本身、从文件名中判断,或者(在一些少见的情况下)用subprocess
做一些命令行操作来获取的。
无论如何,这里有一个存储的例子:
from django.core.files.storage import Storage
class MyStorage(Storage):
...
def get_valid_name(name):
return name.replace('gif', 'jpg')
...
这可能是你有点困惑的地方。Django有自己的存储引擎,会自动把文件写入你的MEDIA_ROOT
目录。所以,你的代码片段
def upload_path(self, instance, upload_filename):
return ???
其实是多余的(如果你定义了自己的存储的话;)现在明白了吗?django.core.files.storage.Storage是来帮助你的。
首先,你上面粘贴的代码片段来自于 django 表单。需要注意的是,Django 有两种字段类:模型字段和 表单字段。它们的名字通常是一样的,但实现和目的完全不同。在你的例子中,你试图将从 表单 字段复制的代码用在自定义的 模型 字段上。
对你来说,真正有趣的代码在这里:https://github.com/django/django/blob/master/django/core/files/images.py - 这是提取图像尺寸的代码片段。混合类 ImageFile
在这里被使用:https://github.com/django/django/blob/master/django/db/models/fields/files.py 中的 ImageFieldFile
。后者被用作 ImageField
模型字段类中的属性类。
所以,你需要做的就是:
- 复制
django/core/files/images.py
并修改它,以提取不仅是尺寸,还有文件类型(比如,你可以把这个混合类命名为ImageFileWithType
)。 - 重写
ImageFieldFile
,提供你自定义的混合类; - 重写
ImageField
类,替换其中的属性类。
from your_custom_fields_module import ImageFileWithType
from django.db.models.fields import ImageFieldFile, ImageField
class ImageFieldFileWithType(ImageFileWithType, ImageFieldFile):
"""As `ImageFileWithType` mixin is at the beginning,
it overrides default mixin"""
pass
class ImageFieldWithType(ImageField):
attr_class = ImageFieldFileWithType
然后就可以在你的模型中使用这个模型字段了!注意,如果你使用的是 South 迁移,你需要特别注意处理自定义模型字段。
希望这对你有帮助!