将Pillow创建的图像添加到单个Django对象中

时间:2017-06-14 11:00:11

标签: python django

我写了一个小应用程序,它将一个.gif作为上传,并使用Pillow将gif分割成帧。我通过Document模型保存.gif文件,并通过DocumentImage模型保存框架。目前,该应用程序保存.gif对象并为每个框架创建一个对象。我想要的是将所有帧保存到单个对象中,并将该对象链接到gif对象。这是我到目前为止所做的:

views.py

def create_gif(uploadedFile):
# create a folder if it doesn't exist
try:
    gif = Image.open('media/' + uploadedFile)
    print()
except:
    print('Not OK')

frames = [frame.copy() for frame in ImageSequence.Iterator(gif)]
i = 0
while (i < len(frames)):
    buffer = BytesIO()
    frames[i].convert('RGB').save(fp=buffer, format='JPEG')
    finalImage = InMemoryUploadedFile(buffer, None, os.path.basename(uploadedFile)[:-4] + str(i) + '.png', 'image/jpeg', frames[i].tell, None)
    imageToSave = DocumentImage(imagefile=finalImage)
    imageToSave.save()
    i += 1


def list(request):
# Handle file upload
if request.method == 'POST':
    form = DocumentForm(request.POST, request.FILES)
    if form.is_valid():
        newdoc = Document(docfile=request.FILES['docfile'])
        if os.path.splitext(newdoc.docfile.name)[1].lower() != '.gif':
            messages.add_message(request, messages.INFO, "Please select a gif")
        else:
            newdoc.save()
            uploadedFile = newdoc.docfile.name
            create_gif(uploadedFile)
            messages.add_message(request, messages.INFO, "Saved")
            return HttpResponseRedirect(reverse('list'))
else:
    form = DocumentForm()  # A empty, unbound form

# Load documents for the list page
documents = Document.objects.all()

# Render list page with the documents and the form
return render(
    request,
    'list.html',
    {'documents': documents, 'form': form}
)

models.py

def content_file_name(instance, filename):
ext = ''.join(filename.split())[:-4]
foldername = "%s/%s" % (uuid.uuid4(), ext)
return '/'.join(['documents', str(foldername), filename])

class Document(models.Model):
     docfile = models.ImageField(upload_to=content_file_name)

class DocumentImage(models.Model):
     imagefile = models.ImageField(upload_to=content_file_name)
     image = models.ForeignKey(Document, related_name='Image', null=True, on_delete=models.CASCADE)

我是Django的新手,不知道我问的问题是否可能,但它似乎是一个基本功能。我已经阅读了关于多对一关系的Django文档,但不太了解外键是如何链接的。谢谢你的时间。

1 个答案:

答案 0 :(得分:1)

  

我想要的是将所有帧保存到单个对象中

我认为你的意思是&#34;每一帧都被保存到一个不同的对象中。 ?

  

并将该对象链接到gif对象。

有点OT,但使用正确的命名确实有帮助。在这里你提到一个&#34; gif对象&#34;但是您的模型名称(带有上传的gif文件的名称)是&#34; Document&#34;,并且各个帧的模型是&#34; DocumentImage&#34; - 但这些是由名为&#34; create_gif&#34;的函数创建的。这既不会创建一个gif而不是Document,但会将gif拆分成帧并创建DocumentImage个实例......它只会导致混乱。

现在回到你的问题 - 根据我的理解,DocumentImage实例没有链接到匹配的Document实例。实际上,您几乎就在那里,您只需将Document实例传递给DocumentImage个实例。最简单的方法是将它传递给你的(名字很差的)create_gif()函数,然后传递给DocumentImage构造函数。

现在你的代码存在很多其他问题 - 不一定是&#34;阻塞&#34;问题(你的代码可能有效)但它真的没有充分利用Python和Django的功能 - 而且有些评论充其量也是误导性的(例如&#34;#如果它没有&#创建一个文件夹) #39; t存在&#34;当您实际打开图像文件时)。

第一个明显的问题是使用表格(您没有发布)但在视图中进行额外验证 - 验证是您表格的首要任务。

第二个问题是依赖文件扩展名进行文件类型验证 - 这是不可靠和不安全的。您可能希望使用类似imghdr的内容。

第三个问题是,当您可以使用ModelForm时,手动创建Document实例。

然后你有文件名/扩展/路径处理代码既不可靠也不可移植,一个裸的except子句不仅隐藏有用的调试内容,而且无法正确处理异常,这个&#34;手册&#34 ; while create_gif()中的while循环,当你应该使用for循环和enumerate来获取索引时,循环中的函数调用并不依赖于循环的变量( IOW:这将在每次迭代时产生相同的结果。)

这是您的代码的修订版。 它完全没有经过测试(这意味着非常肯定有错误),但它可以帮助您解决问题并提高代码的质量。

模型

import os
import sys
import uuid

# etc - add missing imports here

def content_file_name(instance, filename):
    basename, _ext = os.path.splitext(filename)
    foldername = os.path.join(uuid.uuid4(), basename)
    return os.path.join('documents', foldername, filename)


class Document(models.Model):
     docfile = models.ImageField(upload_to=content_file_name)

     def create_documentfiles(self):
         if self.images.exists():
             # XXX should be using the `logging` module instead
             print >> sys.stderr, "Document {} ({}) already has images".format(self.pk, self.docfile.path)
             return

         gif = Image.open(self.docfile.path)
         frames = [frame.copy() for frame in ImageSequence.Iterator(gif)]
         basename, _ext = os.path.splitext(self.docfile.name)
         for index, frame in enumerate(frames):
             buffer = BytesIO()
             item.convert('RGB').save(fp=buffer, format='JPEG')
             destname = "{}{}.png".format(basename, index)
             imagefile = SimpleUploadedFile(buffer.read(), destname, 'image/jpeg')
             DocumentFile.objects.create(document=self, imagefile=imagefile)


class DocumentImage(models.Model):
     imagefile = models.ImageField(upload_to=content_file_name)
     document = models.ForeignKey(Document, related_name='images', on_delete=models.CASCADE)

形式

import imghdr
from django import forms
from myapp.models import Document

class DocumentForm(forms.ModelForm):
    class meta:
        model = Document

    def validate_docfile(self):
        file = self.cleaned_data.get("docfile")
        if file:
            if imghdr.what(file.read()) != "gif":
                raise forms.ValidationError("Please upload a .gif file")
            file.seek(0)
        return file

视图

# better not to name anything `list` - it would shadow the builtin
# `list` type    
def documentlist(request):
    if request.method == 'POST':
        form = DocumentForm(request.POST, request.FILES)
        if form.is_valid():
            newdoc = form.save()
            newdoc.create_documentfiles()
            messages.add_message(request, messages.INFO, "Saved")
            return HttpResponseRedirect(reverse('list'))
    else:
        form = DocumentForm()  

    documents = Document.objects.all()

    return render(
        request,
        'list.html',
        {'documents': documents, 'form': form}
    )

编辑:您在评论中说明:

  

我希望在一个对象下有多个图像。

如果您的意思是将所有帧图像作为同一模型实例的不同字段,那么不,您不能。好吧,你可以为你的模型添加数千ImageField,但无论你添加了多少ImageField,有一天你仍然达到了限制(那里&# 39; s AFAIK对gif中的帧数没有理论限制,如果有技术问题,它可能对rdbms来说太高了支持它,2 /它将是一个糟糕的kludge由于数据库表中的字段数量,处理和/ /你会受到非常严重的性能影响。

总而言之,你做了什么(一个&#34;主人&#34; Document模型与0-N相关DocumentImage slave模型)是正确的方法为您的用例建模。请注意,一旦在Document个实例中正确设置DocumentImage外键,获取给定Document实例的帧就像mydoc.images.all()一样简单(nb使用我更正的模型),并且DocumentImage保证属于一个Document(这里再次使用我的正确型号代码),因此您在技术上接近于&#34;在一个单一对象下的多个图像&#34;你可以得到。在纯Python中的FWIW(没有rdbms和Django模型)你仍然将它建模为一个Document对象,其中list有一个集合(image将是一个明显的候选者)(框架)对象所以它没有那么大的差异。