Hi,
I have the same problem with django 1.7b4. It is defenitely a bug.

среда, 14 мая 2014 г., 8:18:44 UTC+4 пользователь James Boyden написал:
>
> Hi all,
>
> Can anyone confirm that the following is a bug in Django (and that I'm not 
> simply missing the correct way to do this)?
>
> I'm using Django 1.7 on Python 3.4.
>
> I'm new to Django but a longtime Python programmer.  I've been digging 
> into a ValueError I get when I supply a BytesIO instance as the `file` 
> parameter to an ImageFile instance, then supply that ImageFile instance as 
> the `content` parameter to `FieldFile.save`.
>
> In summary:  The method `FieldFile.save` saves `content` to `self.storage` 
> (which causes `content` to be closed) but then attempts to access the size 
> of `content`.  This causes a ValueError("I/O operation on closed file.") to 
> be raised if the internal file inside the ImageFile instance is a BytesIO 
> instance.  Here is `FieldFile.save`:
>   
> https://github.com/django/django/blob/master/django/db/models/fields/files.py#L86
>
> ----
>
> This appears to be an interaction between the following two changesets:
>   - 8 months ago:  
> https://github.com/django/django/commit/30fc49a7ca0d030c7855f31ed44395903fa6abdd
>   - 3 years ago:  
> https://github.com/django/django/commit/88ff6567310d809abddb97adebf04d5e9403ca8a
>
> This changeset also touches relevant code, but I don't think it altered 
> any logic relevant to this problem:
>   - 2 months ago:  
> https://github.com/django/django/commit/96fc3908ad25f40b4c6e2f76be2639f665223227
>
> ----
>
> Here's the detailed line-by-line trace:
>
> In "django/db/models/fields/files.py", in method `FieldFile.save(self, 
> name, content, save=True)`:
>   
> https://github.com/django/django/blob/master/django/db/models/fields/files.py#L86
> this function invokes `self.storage.save(name, content)` [line 88],
> then assigns `content.size` to `self._size` [line 92].
>
> In "django/core/files/storage.py", in method `Storage.save(self, name, 
> content)`:
>   
> https://github.com/django/django/blob/master/django/core/files/storage.py#L37
> this function invokes `self._save(name, content)` [line 51].
>
> In "django/core/files/storage.py", in method 
> `FileSystemStorage._save(self, name, content)`:
>   
> https://github.com/django/django/blob/master/django/core/files/storage.py#L175
> this function invokes `content.close()` [line 231]:
>   
> https://github.com/django/django/blob/master/django/core/files/storage.py#L231
>
> At this point, `content` is now closed.
>
> But then back to method `FieldFile.save(self, name, content, save=True)`, 
> line 92:
>   
> https://github.com/django/django/blob/master/django/db/models/fields/files.py#L92
> we next access the `size` attribute of `content`.
>
> The type of `content` is ImageFile, which inherits File:
>   - class ImageFile:  
> https://github.com/django/django/blob/master/django/core/files/images.py#L11
>   - class File:  
> https://github.com/django/django/blob/master/django/core/files/base.py#L13
>
> Accessing the `size` attribute of `content` accesses the `size` property:
>   
> https://github.com/django/django/blob/master/django/core/files/base.py#L64
> which invokes the `File._get_size(self)` method:
>   
> https://github.com/django/django/blob/master/django/core/files/base.py#L55
>
> At this point, `content` (a recently-created ImageFile instance) doesn't 
> have a `_size` attribute, so `self._get_size_from_underlying_file()` is 
> invoked [line 58]:
>
> In method `File._get_size_from_underlying_file()`, line 39:
>   
> https://github.com/django/django/blob/master/django/core/files/base.py#L39
> the function checks for various attributes of the underlying Python file 
> in a chained if-statement.
>
> In my case, the underlying Python file is a BytesIO instance:
>   https://docs.python.org/3/library/io.html#io.BytesIO
> which inherits io.BufferedIOBase:
>   https://docs.python.org/3/library/io.html#io.BufferedIOBase
> which in turn inherits io.IOBase:
>   https://docs.python.org/3/library/io.html#io.IOBase
>
> The attributes checked, and the results when the file is a BytesIO 
> instance:
>   - `size`:  no
>   - `name`:  no
>   - `tell` and `seek`:  yes, in io.IOBase
>
> Because the `tell` and `seek` attributes were found, the function attempts 
> to determine the file size using `self.file.tell()` [line 48]:
>   
> https://github.com/django/django/blob/master/django/core/files/base.py#L48
>
> However, at this point, because the BytesIO instance is closed, this will 
> result in a ValueError("I/O operation on closed file.") being raised.
>
> This ValueError exception is raised in the Python 3.4 source, in the file 
> "Python-3.4.0/Modules/_io/bytesio.c", when the macro `CHECK_CLOSED(self)` 
> [line 22] is invoked by function `bytesio_tell(bytesio *self)` [line 254].
>
> ----
>
> At this point, my work-around is to set the `_size` attribute of the 
> ImageFile manually (to the BytesIO size calculated using the same method as 
> in `File._get_size_from_underlying_file()`) before invoking 
> `FieldFile.save`.
>
>         def get_bytesio_size(buf):
>             """Calculate the size of BytesIO instance `buf`.
>             Note: Ensure `buf` is not already closed!
>             """
>             # This approach was copied from "django/core/files/base.py"
>             pos = buf.tell()
>             buf.seek(0, os.SEEK_END)
>             size = buf.tell()
>             buf.seek(pos)
>             return size
>
>         img_file._size = get_bytesio_size(bytesio)
>
>         # Then img_field.save(img_name, img_file), etc...
>
>
> It works, but obviously it's hacky to do this.
>
> Can anyone confirm that this problem is indeed a bug in Django (and that 
> I'm not simply missing the correct way to do this)?
>
> Thanks,
> jb
>  

-- 
You received this message because you are subscribed to the Google Groups 
"Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/django-users.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-users/14234b3f-b08d-49c4-a1de-34fd354c9882%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to