5

我正在尝试为存储在 django 模型中的“覆盖”配置生成预览,而不是稍后应用于其他模型。我没有太多使用 python 操作文件的经验... =(

这是我的代码:

import io
from django.conf import settings
from django.db import models
from wand.image import Image
from PIL.ImageFile import ImageFile, Parser, Image as PilImage

class Overlay(models.Model):
    RELATIVE_POSITIONS = (...)
    SIZE_MODES = (...)

    name = models.CharField(max_length=50)
    source = models.FileField(upload_to='overlays/%Y/%m/%d')
    sample = models.ImageField(upload_to='overlay_samples/%Y/%m/%d', blank=True)
    px = models.SmallIntegerField(default=0)
    py = models.SmallIntegerField(default=0)
    position = models.CharField(max_length=2, choices=RELATIVE_POSITIONS)
    width = models.SmallIntegerField(default=0)
    height = models.SmallIntegerField(default=0)
    size_mode = models.CharField(max_length=1, choices=SIZE_MODES, default='B')
    last_edit = models.DateTimeField(auto_now=True)

    def generate_sample(self):
        """
        Generates the sample image and saves it in the "sample" field model
        :return: void
        """
        base_pic = Image(filename=os.path.join(settings.BASE_DIR, 'girl.jpg'))
        overlay_pic = Image(file=self.source)
        result_pic = io.BytesIO()
        pil_parser = Parser()

        if self.width or self.height:
            resize_args = {}
            if self.width:
                resize_args['width'] = self.width
            if self.height:
                resize_args['height'] = self.height
            overlay_pic.resize(**resize_args)
            base_pic.composite(overlay_pic, self.px, self.py)
            base_pic.save(file=result_pic)

        result_pic.seek(0)
        while True:
            s = result_pic.read(1024)
            if not s:
                break
            pil_parser.feed(s)

        pil_result_pic = pil_parser.close()
        self.sample.save(self.name, pil_result_pic, False)

    def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
        self.generate_sample()
        super(Overlay, self).save(force_insert, force_update, using, update_fields)

但是我在这里读到的 AttributeError 是我的 django 调试数据的一部分:

 /usr/local/lib/python2.7/dist-packages/django/core/files/utils.py in <lambda>

    """
    encoding = property(lambda self: self.file.encoding)
    fileno = property(lambda self: self.file.fileno)
    flush = property(lambda self: self.file.flush)
    isatty = property(lambda self: self.file.isatty)
    newlines = property(lambda self: self.file.newlines)
    read = property(lambda self: self.file.read)
    readinto = property(lambda self: self.file.readinto)
    readline = property(lambda self: self.file.readline)
    readlines = property(lambda self: self.file.readlines)
    seek = property(lambda self: self.file.seek)
    softspace = property(lambda self: self.file.softspace)
    tell = property(lambda self: self.file.tell)

▼ 局部 vars 变量值

self    <File: None>



/usr/local/lib/python2.7/dist-packages/PIL/Image.py in __getattr__

        # numpy array interface support
        new = {}
        shape, typestr = _conv_type_shape(self)
        new['shape'] = shape
        new['typestr'] = typestr
        new['data'] = self.tobytes()
        return new
    raise AttributeError(name)

def __getstate__(self):
    return [
        self.info,
        self.mode,
        self.size,

▼ 局部 vars 变量值

self    <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=1080x1618 at 0x7F1429291248>
name    'read'

怎么了?

4

3 回答 3

3

解决了!

例如@Alexey Kuleshevich 说django FileField需要一个 File objeto,但缺少的是我们必须首先将图像保存到磁盘或内存中的文件中,因为会猜测它是更好的内存......所以这是最终解决方案。我认为可以改进不使用两步“转换”

from django.core.files.base import ContentFile

并在方法内:

    result_pic = io.BytesIO()
    pil_parser = Parser()

    ...
    overlay_pic.resize(**resize_args)
    base_pic.composite(overlay_pic, self.px, self.py)
    base_pic.save(file=result_pic)

    result_pic.seek(0)
    while True:
        s = result_pic.read(1024)
        if not s:
            break
        pil_parser.feed(s)

    result_pic = io.BytesIO()
    pil_result_pic = pil_parser.close()
    pil_result_pic.save(result_pic, format='JPEG')
    django_file = ContentFile(result_pic.getvalue())
    self.sample.save(self.name, django_file, False)

感谢这个答案: 如何将 PIL 转换Image为 Django File

于 2015-01-31T18:43:21.253 回答
1

每当您将文件保存到ImageField或者FileField您需要确保它是 Django 的File对象时。这里参考文档:https ://docs.djangoproject.com/en/1.7/ref/models/fields/#filefield-and-fieldfile

from django.core.files import File

并在一个方法内:

def generate_sample(self):
    ...
    pil_result_pic = pil_parser.close()
    self.sample.save(self.name, File(pil_result_pic), False)

否则它看起来不错,虽然我可能错过了一些东西。尝试一下,看看它是否解决了问题,如果没有,我会更深入地研究它。

编辑

您实际上不需要解析器。我认为这应该解决它:

from django.core.files import ContentFile

class Overlay(models.Model):
    ...

    def generate_sample(self):
        base_pic = Image(filename=os.path.join(settings.BASE_DIR, 'girl.jpg'))
        overlay_pic = Image(file=self.source)
        result_pic = io.BytesIO()

        if self.width or self.height:
            resize_args = {}
            if self.width:
                resize_args['width'] = self.width
            if self.height:
                resize_args['height'] = self.height
            overlay_pic.resize(**resize_args)
        base_pic.composite(overlay_pic, self.px, self.py)
        base_pic.save(file=result_pic)

        content = result_pic.getvalue()
        self.sample.save(self.name, ContentFile(content), False)
        result_pic.close()
        base_pic.close()
        overlay_pic.close()

有一件事可能是一个潜在的问题,它会在每次Overlay保存模型时执行此操作,即使原始图像相同。但如果它很少保存,它不应该是一个问题。

于 2015-01-31T01:28:39.480 回答
1

以防万一,这是一个更优雅(在我看来)的实现。首先它需要这个应用程序:django-smartfields。此解决方案如何更好:

  • sample仅在source字段更改时更新字段,并且仅在保存模型之前。
  • 如果keep_orphans省略,旧source文件将被清理。

实际代码:

import os
from django.conf import settings
from django.db import models
from django.utils import six

from smartfields import fields
from smartfields.dependencies import FileDependency
from smartfields.processors import WandImageProcessor
from wand.image import Image

class CustomImageProcessor(WandImageProcessor):

    def resize(self, image, scale=None, instance=None, **kwargs):
        scale = {'width': instance.width, 'height': instance.height}
        return super(CustomImageProcessor, self).resize(
            image, scale=scale, instance=instance, **kwargs)

    def convert(self, image, instance=None, **kwargs):
        base_pic = Image(filename=os.path.join(settings.BASE_DIR, 'girl.jpg'))
        base_pic.composite(image, instance.px, instance.py)
        stream_out = super(CustomImageProcessor, self).convert(
            image, instance=instance, **kwargs):
        if stream_out is None:
            stream_out = six.BytesIO()
            base_pic.save(file=stream_out)
        return stream_out        


class Overlay(models.Model):
    RELATIVE_POSITIONS = (...)
    SIZE_MODES = (...)

    name = models.CharField(max_length=50)
    source = fields.ImageField(upload_to='overlays/%Y/%m/%d', dependencies=[
        FileDependency(attname='sample', processor=CustomImageProcessor())
    ], keep_orphans=True)
    sample = models.ImageField(upload_to='overlay_samples/%Y/%m/%d', blank=True)
    px = models.SmallIntegerField(default=0)
    py = models.SmallIntegerField(default=0)
    position = models.CharField(max_length=2, choices=RELATIVE_POSITIONS)
    width = models.SmallIntegerField(default=0)
    height = models.SmallIntegerField(default=0)
    size_mode = models.CharField(max_length=1, choices=SIZE_MODES, default='B')
    last_edit = models.DateTimeField(auto_now=True)
于 2015-01-31T23:12:49.917 回答