diff --git a/django/core/files/base.py b/django/core/files/base.py index 48d0be43f0..29dbb6fcc1 100644 --- a/django/core/files/base.py +++ b/django/core/files/base.py @@ -36,8 +36,13 @@ class File(FileProxyMixin): if not hasattr(self, '_size'): if hasattr(self.file, 'size'): self._size = self.file.size - elif os.path.exists(self.file.name): + elif hasattr(self.file, 'name') and os.path.exists(self.file.name): self._size = os.path.getsize(self.file.name) + elif hasattr(self.file, 'tell') and hasattr(self.file, 'seek'): + pos = self.file.tell() + self.file.seek(0, os.SEEK_END) + self._size = self.file.tell() + self.file.seek(pos) else: raise AttributeError("Unable to determine the file's size.") return self._size @@ -61,12 +66,12 @@ class File(FileProxyMixin): if hasattr(self, 'seek'): self.seek(0) - # Assume the pointer is at zero... - counter = self.size - while counter > 0: - yield self.read(chunk_size) - counter -= chunk_size + while True: + data = self.read(chunk_size) + if not data: + break + yield data def multiple_chunks(self, chunk_size=None): """ diff --git a/tests/regressiontests/file_storage/tests.py b/tests/regressiontests/file_storage/tests.py index f03a7a8eac..c28b019c84 100644 --- a/tests/regressiontests/file_storage/tests.py +++ b/tests/regressiontests/file_storage/tests.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from __future__ import absolute_import + import errno import os import shutil @@ -17,12 +19,13 @@ except ImportError: from django.conf import settings from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured -from django.core.files.base import ContentFile +from django.core.files.base import File, ContentFile from django.core.files.images import get_image_dimensions from django.core.files.storage import FileSystemStorage, get_storage_class from django.core.files.uploadedfile import UploadedFile from django.test import SimpleTestCase from django.utils import unittest +from ..servers.tests import LiveServerBase # Try to import PIL in either of the two ways it can end up installed. # Checking for the existence of Image is enough for CPython, but @@ -544,6 +547,42 @@ class ContentFileTestCase(unittest.TestCase): def test_content_file_default_name(self): self.assertEqual(ContentFile("content").name, None) - def test_content_file_custome_name(self): + def test_content_file_custom_name(self): name = "I can have a name too!" self.assertEqual(ContentFile("content", name=name).name, name) + +class NoNameFileTestCase(unittest.TestCase): + """ + Other examples of unnamed files may be tempfile.SpooledTemporaryFile or + urllib.urlopen() + """ + def test_noname_file_default_name(self): + self.assertEqual(File(StringIO('A file with no name')).name, None) + + def test_noname_file_get_size(self): + self.assertEqual(File(StringIO('A file with no name')).size, 19) + +class FileLikeObjectTestCase(LiveServerBase): + """ + Test file-like objects (#15644). + """ + def setUp(self): + self.temp_dir = tempfile.mkdtemp() + self.storage = FileSystemStorage(location=self.temp_dir) + + def tearDown(self): + shutil.rmtree(self.temp_dir) + + def test_urllib2_urlopen(self): + """ + Test the File storage API with a file like object coming from urllib2.urlopen() + """ + + file_like_object = self.urlopen('/example_view/') + f = File(file_like_object) + stored_filename = self.storage.save("remote_file.html", f) + + stored_file = self.storage.open(stored_filename) + remote_file = self.urlopen('/example_view/') + + self.assertEqual(stored_file.read(), remote_file.read())