Commit d725cc97 authored by Jacob Kaplan-Moss's avatar Jacob Kaplan-Moss
Browse files

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
parent ef76102e
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -59,7 +59,7 @@ answer newbie questions, and generally made Django that much better:
    Arthur <avandorp@gmail.com>
    av0000@mail.ru
    David Avsajanishvili <avsd05@gmail.com>
    axiak@mit.edu
    Mike Axiak <axiak@mit.edu>
    Niran Babalola <niran@niran.org>
    Morten Bagai <m@bagai.com>
    Mikaël Barbero <mikael.barbero nospam at nospam free.fr>
@@ -141,7 +141,9 @@ answer newbie questions, and generally made Django that much better:
    Marc Fargas <telenieko@telenieko.com>
    Szilveszter Farkas <szilveszter.farkas@gmail.com>
    favo@exoweb.net
    fdr <drfarina@gmail.com>
    Dmitri Fedortchenko <zeraien@gmail.com>
    Jonathan Feignberg <jdf@pobox.com>
    Liang Feng <hutuworm@gmail.com>
    Bill Fenner <fenner@gmail.com>
    Stefane Fermgier <sf@fermigier.com>
+15 −0
Original line number Diff line number Diff line
@@ -231,6 +231,21 @@ MEDIA_ROOT = ''
# Example: "http://media.lawrence.com"
MEDIA_URL = ''

# List of upload handler classes to be applied in order.
FILE_UPLOAD_HANDLERS = (
    'django.core.files.uploadhandler.MemoryFileUploadHandler',
    'django.core.files.uploadhandler.TemporaryFileUploadHandler',
)

# Maximum size, in bytes, of a request before it will be streamed to the
# file system instead of into memory.
FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440 # i.e. 2.5 MB

# Directory in which upload streamed files will be temporarily saved. A value of
# `None` will make Django use the operating system's default temporary directory
# (i.e. "/tmp" on *nix systems).
FILE_UPLOAD_TEMP_DIR = None

# Default formatting for date objects. See all available format strings here:
# http://www.djangoproject.com/documentation/templates/#now
DATE_FORMAT = 'N j, Y'
+0 −0

Empty file added.

+66 −0
Original line number Diff line number Diff line
"""
Portable file locking utilities.

Based partially on example by Jonathan Feignberg <jdf@pobox.com> in the Python
Cookbook, licensed under the Python Software License.

    http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65203

Example Usage::

    >>> from django.core.files import locks
    >>> f = open('./file', 'wb')
    >>> locks.lock(f, locks.LOCK_EX)
    >>> f.write('Django')
    >>> f.close()
"""

__all__ = ('LOCK_EX','LOCK_SH','LOCK_NB','lock','unlock')

system_type = None

try:
    import win32con
    import win32file
    import pywintypes
    LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
    LOCK_SH = 0
    LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
    __overlapped = pywintypes.OVERLAPPED()
    system_type = 'nt'
except (ImportError, AttributeError):
    pass

try:
    import fcntl
    LOCK_EX = fcntl.LOCK_EX
    LOCK_SH = fcntl.LOCK_SH
    LOCK_NB = fcntl.LOCK_NB
    system_type = 'posix'
except (ImportError, AttributeError):
    pass

if system_type == 'nt':
    def lock(file, flags):
        hfile = win32file._get_osfhandle(file.fileno())
        win32file.LockFileEx(hfile, flags, 0, -0x10000, __overlapped)

    def unlock(file):
        hfile = win32file._get_osfhandle(file.fileno())
        win32file.UnlockFileEx(hfile, 0, -0x10000, __overlapped)
elif system_type == 'posix':
    def lock(file, flags):
        fcntl.flock(file.fileno(), flags)

    def unlock(file):
        fcntl.flock(file.fileno(), fcntl.LOCK_UN)
else:
    # File locking is not supported.
    LOCK_EX = LOCK_SH = LOCK_NB = None

    # Dummy functions that don't do anything.
    def lock(file, flags):
        pass

    def unlock(file):
        pass
+59 −0
Original line number Diff line number Diff line
"""
Move a file in the safest way possible::

    >>> from django.core.files.move import file_move_save
    >>> file_move_save("/tmp/old_file", "/tmp/new_file")
"""

import os
from django.core.files import locks

__all__ = ['file_move_safe']

try:
    import shutil
    file_move = shutil.move
except ImportError:
    file_move = os.rename

def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_overwrite=False):
    """
    Moves a file from one location to another in the safest way possible.

    First, try using ``shutils.move``, which is OS-dependent but doesn't break
    if moving across filesystems. Then, try ``os.rename``, which will break
    across filesystems. Finally, streams manually from one file to another in
    pure Python.

    If the destination file exists and ``allow_overwrite`` is ``False``, this
    function will throw an ``IOError``.
    """

    # There's no reason to move if we don't have to.
    if old_file_name == new_file_name:
        return

    if not allow_overwrite and os.path.exists(new_file_name):
        raise IOError("Cannot overwrite existing file '%s'." % new_file_name)

    try:
        file_move(old_file_name, new_file_name)
        return
    except OSError:
        # This will happen with os.rename if moving to another filesystem
        pass

    # If the built-in didn't work, do it the hard way.
    new_file = open(new_file_name, 'wb')
    locks.lock(new_file, locks.LOCK_EX)
    old_file = open(old_file_name, 'rb')
    current_chunk = None

    while current_chunk != '':
        current_chunk = old_file.read(chunk_size)
        new_file.write(current_chunk)

    new_file.close()
    old_file.close()

    os.remove(old_file_name)
Loading