使用Django的FileField跟踪图像类型

0 投票
3 回答
1626 浏览
提问于 2025-04-18 08:39

我的模型里有一个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 个回答

0

哦,我明白了。抱歉之前没理解清楚。

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'

结果是,不重要。看起来很好!

0

其实,从我能看出来的,你似乎根本不需要用到Pillow库(除非你的项目还有其他没提到的部分)。你只需要定义一个存储系统,然后在取用的时候,访问一下FileFieldFieldFile。我推荐这种方法,因为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是来帮助你的。

2

首先,你上面粘贴的代码片段来自于 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 模型字段类中的属性类。

所以,你需要做的就是:

  1. 复制 django/core/files/images.py 并修改它,以提取不仅是尺寸,还有文件类型(比如,你可以把这个混合类命名为 ImageFileWithType)。
  2. 重写 ImageFieldFile,提供你自定义的混合类;
  3. 重写 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 迁移,你需要特别注意处理自定义模型字段。

希望这对你有帮助!

撰写回答