Django与PIL - '_io.BytesIO'对象没有'name'属性

3

我在保存上传的照片之前使用PIL进行调整大小。请注意,我正在使用formsets上传图片。我使用BytesIO打开文件。在最后一步时,我会遇到错误 - '_io.BytesIO' object has no attribute 'name'。为什么会出现这种情况?

def fsbo_create_listing(request):
    PhotoFormSet = formset_factory(OwnerListingPhotoForm, extra=15)
    if request.method == 'POST':
        form = OwnerListingForm(request.POST)
        photo_formset = PhotoFormSet(request.POST, request.FILES)
        if form.is_valid() and photo_formset.is_valid():
            form.instance.user = request.user
            form.save()
            for i in photo_formset:
                if i.instance.pk and i.instance.photo == '':
                    i.instance.delete()
                elif i.cleaned_data:
                    temp = i.save(commit=False)
                    temp.listing = form.instance
                    temp.save() # Where the error happens

def clean_photo(self):
    picture = self.cleaned_data.get('photo')
    # I had to import ImageFieldFile.  If picture is already uploaded, picture would still be retrieved as ImageFieldFile.  The following line checks the variable type of `picture` to determine whether the cleaning should proceed.
    if type(picture) != ImageFieldFile:
        image_field = self.cleaned_data.get('photo')
        image_file = BytesIO(image_field.read())
        image = Image.open(image_file)
        image = ImageOps.fit(image, (512,512,), Image.ANTIALIAS)
        image_file = BytesIO()
        image.save(image_file, 'JPEG', quality=90)
        image_field.file = image_file
        #if picture._size > 2*1024*1024:
            #raise ValidationError("Image file too large.  Max size is 2MB.")
    return picture

class OwnerListingPhoto(models.Model):

    listing = models.ForeignKey(OwnerListing, on_delete=models.CASCADE, related_name='owner_listing_photo')
    photo = models.ImageField(upload_to=owner_listing_folder_name)
2个回答

4
问题在于Django的新版本默认使用MemoryFileUploadHandler,它不会创建临时文件,因此没有文件“名称”。请参见相关的Django票证。 你可能需要稍微修改一下代码才能使其工作,但你至少可以通过设置以下内容来开始获取名称属性:
FILE_UPLOAD_HANDLERS = [
    'django.core.files.uploadhandler.TemporaryFileUploadHandler',
]

在您的settings.py文件中。 您可能会发现我用来解决几乎完全相同问题的代码很有帮助。
def clean_logo_file(self):
    logo_file_field = self.cleaned_data.get('logo_file')
    if logo_file_field:
        try:
            logo_file = logo_file_field.file
            with Image.open(logo_file_field.file.name) as image:
                image.thumbnail((512, 512), Image.ANTIALIAS)
                image.save(logo_file, format=image.format)
                logo_file_field.file = logo_file
                return logo_file_field
        except IOError:
            logger.exception("Error during image resize.")

有关上传处理程序的其他信息。


谢谢你的回答。实际上,我最终通过在保存照片后修改它来解决了这个问题。 - Jack

0
  • 如果文件大小超过2.5MB(2621440字节)- Django将使用TemporaryFileUploadHandler

  • 否则,Django将使用MemoryFileUploadHandler

您可以在settings.py中更改FILE_UPLOAD_MAX_MEMORY_SIZEdoc
或更改FILE_UPLOAD_HANDLERSdoc),如上所述的Nostalg.io
我的例子用Django Rest Framework serializers:

代码有误:

# models.py
class ImageModel(Model):
    image = models.ImageField(upload_to='images/', null=False, blank=False)

# serializers.py
class ImageSerializer(serializers.ModelSerializer):

    class Meta:
        model = ImageModel
        fields = ["id", "image"]
        read_only_fields = ["id"]

    def validate_image(self, user_img):
        img = Image.open(user_img)
        ...  # process image here
        img_io = io.BytesIO()
        img.save(img_io, format='JPEG', quality=100)
        filename = "%s.jpg" % user_img.name.split('.')[0]

        user_img.name = "%s.jpg" % user_img.name.split('.')[0]
        user_img.file = img_io  # BAD IDEA!!!
        # This overrides django's tempfile._TemporaryFileWrapper() with _io.BytesIO() !!!
        ...
        return user_img  # if picture bigger than 2.5mb -> gives an error!

修复后的代码:

#settings.py
FILE_UPLOAD_HANDLERS = [
    'django.core.files.uploadhandler.TemporaryFileUploadHandler',
]

# serializers.py
class ImageSerializer(serializers.ModelSerializer):

    class Meta:
        model = ImageModel
        fields = ["id", "image"]
        read_only_fields = ["id"]

    def validate_image(self, user_img):
        img = Image.open(user_img)

        ...  # process image here

        # override old TemporaryFile image with edited image
        path_to_tmp = user_img.file.name
        new_filename = "%s.jpeg" % user_img.name.split('.')[0]

        # set new image name
        img.save(path_to_tmp, format='JPEG', quality=100)
        user_img.name = new_filename
        ...
        return user_img  # no errors more :)

重写models.py中的save()方法来处理图像可能更加合理,但由于方便使用ValidationError(),我选择在serializers.py中转换图像。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接