Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit d725cc9

Browse files
committed
Fixed #2070: refactored Django's file upload capabilities.
A description of the new features can be found in the new [http://www.djangoproject.com/documentation/upload_handing/ upload handling documentation]; the executive summary is that Django will now happily handle uploads of large files without issues. This changes the representation of uploaded files from dictionaries to bona fide objects; see BackwardsIncompatibleChanges for details. git-svn-id: http://code.djangoproject.com/svn/django/trunk@7814 bcc190cf-cafb-0310-a4f2-bffc1f526a37
1 parent ef76102 commit d725cc9

File tree

38 files changed

+2291
-154
lines changed

38 files changed

+2291
-154
lines changed

‎AUTHORS‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ answer newbie questions, and generally made Django that much better:
5959
Arthur <avandorp@gmail.com>
6060
av0000@mail.ru
6161
David Avsajanishvili <avsd05@gmail.com>
62-
axiak@mit.edu
62+
Mike Axiak <axiak@mit.edu>
6363
Niran Babalola <niran@niran.org>
6464
Morten Bagai <m@bagai.com>
6565
Mikaël Barbero <mikael.barbero nospam at nospam free.fr>
@@ -141,7 +141,9 @@ answer newbie questions, and generally made Django that much better:
141141
Marc Fargas <telenieko@telenieko.com>
142142
Szilveszter Farkas <szilveszter.farkas@gmail.com>
143143
favo@exoweb.net
144+
fdr <drfarina@gmail.com>
144145
Dmitri Fedortchenko <zeraien@gmail.com>
146+
Jonathan Feignberg <jdf@pobox.com>
145147
Liang Feng <hutuworm@gmail.com>
146148
Bill Fenner <fenner@gmail.com>
147149
Stefane Fermgier <sf@fermigier.com>

‎django/conf/global_settings.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,21 @@
231231
# Example: "http://media.lawrence.com"
232232
MEDIA_URL = ''
233233

234+
# List of upload handler classes to be applied in order.
235+
FILE_UPLOAD_HANDLERS = (
236+
'django.core.files.uploadhandler.MemoryFileUploadHandler',
237+
'django.core.files.uploadhandler.TemporaryFileUploadHandler',
238+
)
239+
240+
# Maximum size, in bytes, of a request before it will be streamed to the
241+
# file system instead of into memory.
242+
FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440 # i.e. 2.5 MB
243+
244+
# Directory in which upload streamed files will be temporarily saved. A value of
245+
# `None` will make Django use the operating system's default temporary directory
246+
# (i.e. "/tmp" on *nix systems).
247+
FILE_UPLOAD_TEMP_DIR = None
248+
234249
# Default formatting for date objects. See all available format strings here:
235250
# http://www.djangoproject.com/documentation/templates/#now
236251
DATE_FORMAT = 'N j, Y'

‎django/core/files/__init__.py‎

Whitespace-only changes.

‎django/core/files/locks.py‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""
2+
Portable file locking utilities.
3+
4+
Based partially on example by Jonathan Feignberg <jdf@pobox.com> in the Python
5+
Cookbook, licensed under the Python Software License.
6+
7+
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65203
8+
9+
Example Usage::
10+
11+
>>> from django.core.files import locks
12+
>>> f = open('./file', 'wb')
13+
>>> locks.lock(f, locks.LOCK_EX)
14+
>>> f.write('Django')
15+
>>> f.close()
16+
"""
17+
18+
__all__ = ('LOCK_EX','LOCK_SH','LOCK_NB','lock','unlock')
19+
20+
system_type = None
21+
22+
try:
23+
import win32con
24+
import win32file
25+
import pywintypes
26+
LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
27+
LOCK_SH = 0
28+
LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
29+
__overlapped = pywintypes.OVERLAPPED()
30+
system_type = 'nt'
31+
except (ImportError, AttributeError):
32+
pass
33+
34+
try:
35+
import fcntl
36+
LOCK_EX = fcntl.LOCK_EX
37+
LOCK_SH = fcntl.LOCK_SH
38+
LOCK_NB = fcntl.LOCK_NB
39+
system_type = 'posix'
40+
except (ImportError, AttributeError):
41+
pass
42+
43+
if system_type == 'nt':
44+
def lock(file, flags):
45+
hfile = win32file._get_osfhandle(file.fileno())
46+
win32file.LockFileEx(hfile, flags, 0, -0x10000, __overlapped)
47+
48+
def unlock(file):
49+
hfile = win32file._get_osfhandle(file.fileno())
50+
win32file.UnlockFileEx(hfile, 0, -0x10000, __overlapped)
51+
elif system_type == 'posix':
52+
def lock(file, flags):
53+
fcntl.flock(file.fileno(), flags)
54+
55+
def unlock(file):
56+
fcntl.flock(file.fileno(), fcntl.LOCK_UN)
57+
else:
58+
# File locking is not supported.
59+
LOCK_EX = LOCK_SH = LOCK_NB = None
60+
61+
# Dummy functions that don't do anything.
62+
def lock(file, flags):
63+
pass
64+
65+
def unlock(file):
66+
pass

‎django/core/files/move.py‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""
2+
Move a file in the safest way possible::
3+
4+
>>> from django.core.files.move import file_move_save
5+
>>> file_move_save("/tmp/old_file", "/tmp/new_file")
6+
"""
7+
8+
import os
9+
from django.core.files import locks
10+
11+
__all__ = ['file_move_safe']
12+
13+
try:
14+
import shutil
15+
file_move = shutil.move
16+
except ImportError:
17+
file_move = os.rename
18+
19+
def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_overwrite=False):
20+
"""
21+
Moves a file from one location to another in the safest way possible.
22+
23+
First, try using ``shutils.move``, which is OS-dependent but doesn't break
24+
if moving across filesystems. Then, try ``os.rename``, which will break
25+
across filesystems. Finally, streams manually from one file to another in
26+
pure Python.
27+
28+
If the destination file exists and ``allow_overwrite`` is ``False``, this
29+
function will throw an ``IOError``.
30+
"""
31+
32+
# There's no reason to move if we don't have to.
33+
if old_file_name == new_file_name:
34+
return
35+
36+
if not allow_overwrite and os.path.exists(new_file_name):
37+
raise IOError("Cannot overwrite existing file '%s'." % new_file_name)
38+
39+
try:
40+
file_move(old_file_name, new_file_name)
41+
return
42+
except OSError:
43+
# This will happen with os.rename if moving to another filesystem
44+
pass
45+
46+
# If the built-in didn't work, do it the hard way.
47+
new_file = open(new_file_name, 'wb')
48+
locks.lock(new_file, locks.LOCK_EX)
49+
old_file = open(old_file_name, 'rb')
50+
current_chunk = None
51+
52+
while current_chunk != '':
53+
current_chunk = old_file.read(chunk_size)
54+
new_file.write(current_chunk)
55+
56+
new_file.close()
57+
old_file.close()
58+
59+
os.remove(old_file_name)

‎django/core/files/uploadedfile.py‎

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
"""
2+
Classes representing uploaded files.
3+
"""
4+
5+
import os
6+
try:
7+
from cStringIO import StringIO
8+
except ImportError:
9+
from StringIO import StringIO
10+
11+
__all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile')
12+
13+
class UploadedFile(object):
14+
"""
15+
A abstract uploadded file (``TemporaryUploadedFile`` and
16+
``InMemoryUploadedFile`` are the built-in concrete subclasses).
17+
18+
An ``UploadedFile`` object behaves somewhat like a file object and
19+
represents some file data that the user submitted with a form.
20+
"""
21+
DEFAULT_CHUNK_SIZE = 64 * 2**10
22+
23+
def __init__(self, file_name=None, content_type=None, file_size=None, charset=None):
24+
self.file_name = file_name
25+
self.file_size = file_size
26+
self.content_type = content_type
27+
self.charset = charset
28+
29+
def __repr__(self):
30+
return "<%s: %s (%s)>" % (self.__class__.__name__, self.file_name, self.content_type)
31+
32+
def _set_file_name(self, name):
33+
# Sanitize the file name so that it can't be dangerous.
34+
if name is not None:
35+
# Just use the basename of the file -- anything else is dangerous.
36+
name = os.path.basename(name)
37+
38+
# File names longer than 255 characters can cause problems on older OSes.
39+
if len(name) > 255:
40+
name, ext = os.path.splitext(name)
41+
name = name[:255 - len(ext)] + ext
42+
43+
self._file_name = name
44+
45+
def _get_file_name(self):
46+
return self._file_name
47+
48+
file_name = property(_get_file_name, _set_file_name)
49+
50+
def chunk(self, chunk_size=None):
51+
"""
52+
Read the file and yield chucks of ``chunk_size`` bytes (defaults to
53+
``UploadedFile.DEFAULT_CHUNK_SIZE``).
54+
"""
55+
if not chunk_size:
56+
chunk_size = UploadedFile.DEFAULT_CHUNK_SIZE
57+
58+
if hasattr(self, 'seek'):
59+
self.seek(0)
60+
# Assume the pointer is at zero...
61+
counter = self.file_size
62+
63+
while counter > 0:
64+
yield self.read(chunk_size)
65+
counter -= chunk_size
66+
67+
def multiple_chunks(self, chunk_size=None):
68+
"""
69+
Returns ``True`` if you can expect multiple chunks.
70+
71+
NB: If a particular file representation is in memory, subclasses should
72+
always return ``False`` -- there's no good reason to read from memory in
73+
chunks.
74+
"""
75+
if not chunk_size:
76+
chunk_size = UploadedFile.DEFAULT_CHUNK_SIZE
77+
return self.file_size < chunk_size
78+
79+
# Abstract methods; subclasses *must* default read() and probably should
80+
# define open/close.
81+
def read(self, num_bytes=None):
82+
raise NotImplementedError()
83+
84+
def open(self):
85+
pass
86+
87+
def close(self):
88+
pass
89+
90+
# Backwards-compatible support for uploaded-files-as-dictionaries.
91+
def __getitem__(self, key):
92+
import warnings
93+
warnings.warn(
94+
message = "The dictionary access of uploaded file objects is deprecated. Use the new object interface instead.",
95+
category = DeprecationWarning,
96+
stacklevel = 2
97+
)
98+
backwards_translate = {
99+
'filename': 'file_name',
100+
'content-type': 'content_type',
101+
}
102+
103+
if key == 'content':
104+
return self.read()
105+
elif key == 'filename':
106+
return self.file_name
107+
elif key == 'content-type':
108+
return self.content_type
109+
else:
110+
return getattr(self, key)
111+
112+
class TemporaryUploadedFile(UploadedFile):
113+
"""
114+
A file uploaded to a temporary location (i.e. stream-to-disk).
115+
"""
116+
117+
def __init__(self, file, file_name, content_type, file_size, charset):
118+
super(TemporaryUploadedFile, self).__init__(file_name, content_type, file_size, charset)
119+
self.file = file
120+
self.path = file.name
121+
self.file.seek(0)
122+
123+
def temporary_file_path(self):
124+
"""
125+
Returns the full path of this file.
126+
"""
127+
return self.path
128+
129+
def read(self, *args, **kwargs):
130+
return self.file.read(*args, **kwargs)
131+
132+
def open(self):
133+
self.seek(0)
134+
135+
def seek(self, *args, **kwargs):
136+
self.file.seek(*args, **kwargs)
137+
138+
class InMemoryUploadedFile(UploadedFile):
139+
"""
140+
A file uploaded into memory (i.e. stream-to-memory).
141+
"""
142+
def __init__(self, file, field_name, file_name, content_type, charset, file_size):
143+
super(InMemoryUploadedFile, self).__init__(file_name, content_type, charset, file_size)
144+
self.file = file
145+
self.field_name = field_name
146+
self.file.seek(0)
147+
148+
def seek(self, *args, **kwargs):
149+
self.file.seek(*args, **kwargs)
150+
151+
def open(self):
152+
self.seek(0)
153+
154+
def read(self, *args, **kwargs):
155+
return self.file.read(*args, **kwargs)
156+
157+
def chunk(self, chunk_size=None):
158+
self.file.seek(0)
159+
yield self.read()
160+
161+
def multiple_chunks(self, chunk_size=None):
162+
# Since it's in memory, we'll never have multiple chunks.
163+
return False
164+
165+
class SimpleUploadedFile(InMemoryUploadedFile):
166+
"""
167+
A simple representation of a file, which just has content, size, and a name.
168+
"""
169+
def __init__(self, name, content, content_type='text/plain'):
170+
self.file = StringIO(content or '')
171+
self.file_name = name
172+
self.field_name = None
173+
self.file_size = len(content or '')
174+
self.content_type = content_type
175+
self.charset = None
176+
self.file.seek(0)
177+
178+
def from_dict(cls, file_dict):
179+
"""
180+
Creates a SimpleUploadedFile object from
181+
a dictionary object with the following keys:
182+
- filename
183+
- content-type
184+
- content
185+
"""
186+
return cls(file_dict['filename'],
187+
file_dict['content'],
188+
file_dict.get('content-type', 'text/plain'))
189+
190+
from_dict = classmethod(from_dict)

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /