Ticket #2070: 2070-r7695.patch

File 2070-r7695.patch, 100.6 KB (added by Jacob, 16 years ago)

Streaming uploads patch updated to [7695] and reviewed.

  • AUTHORS

    diff --git a/AUTHORS b/AUTHORS
    index 06e80e1..bed524e 100644
    a b 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>
    answer newbie questions, and generally made Django that much better:  
    140140    Marc Fargas <telenieko@telenieko.com>
    141141    Szilveszter Farkas <szilveszter.farkas@gmail.com>
    142142    favo@exoweb.net
     143    fdr <drfarina@gmail.com>
    143144    Dmitri Fedortchenko <zeraien@gmail.com>
     145    Jonathan Feignberg <jdf@pobox.com>
    144146    Liang Feng <hutuworm@gmail.com>
    145147    Bill Fenner <fenner@gmail.com>
    146148    Stefane Fermgier <sf@fermigier.com>
  • django/conf/global_settings.py

    diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
    index 9f24f0c..44d1dfb 100644
    a b MEDIA_ROOT = ''  
    229229# Example: "http://media.lawrence.com"
    230230MEDIA_URL = ''
    231231
     232# List of upload handler classes to be applied in order.
     233FILE_UPLOAD_HANDLERS = (
     234    'django.core.files.uploadhandler.MemoryFileUploadHandler',
     235    'django.core.files.uploadhandler.TemporaryFileUploadHandler',
     236)
     237
     238# Maximum size, in bytes, of a request before it will be streamed to the
     239# file system instead of into memory.
     240FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440 # i.e. 2.5 MB
     241
     242# Directory in which upload streamed files will be temporarily saved. A value of
     243# `None` will make Django use the operating system's default temporary directory
     244# (i.e. "/tmp" on *nix systems).
     245FILE_UPLOAD_TEMP_DIR = None
     246
    232247# Default formatting for date objects. See all available format strings here:
    233248# http://www.djangoproject.com/documentation/templates/#now
    234249DATE_FORMAT = 'N j, Y'
  • new file django/core/files/__init__.py

    diff --git a/django/core/files/__init__.py b/django/core/files/__init__.py
    new file mode 100644
    index 0000000..8b13789
    - +  
     1
  • new file django/core/files/locks.py

    diff --git a/django/core/files/locks.py b/django/core/files/locks.py
    new file mode 100644
    index 0000000..212b51a
    - +  
     1"""
     2Portable file locking utilities.
     3
     4Based partially on example by Jonathan Feignberg <jdf@pobox.com> in the Python
     5Cookbook, licensed under the Python Software License.
     6
     7    http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65203
     8
     9Example 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
     20system_type = None
     21
     22try:
     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'
     31except (ImportError, AttributeError):
     32    pass
     33
     34try:
     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'
     40except (ImportError, AttributeError):
     41    pass
     42
     43if 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)
     51elif 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)
     57else:
     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
  • new file django/core/files/move.py

    diff --git a/django/core/files/move.py b/django/core/files/move.py
    new file mode 100644
    index 0000000..66873d4
    - +  
     1"""
     2Move 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
     8import os
     9from django.core.files import locks
     10
     11__all__ = ['file_move_safe']
     12
     13try:
     14    import shutil
     15    file_move = shutil.move
     16except ImportError:
     17    file_move = os.rename
     18
     19def 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)
  • new file django/core/files/uploadedfile.py

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

    diff --git a/django/core/files/uploadhandler.py b/django/core/files/uploadhandler.py
    new file mode 100644
    index 0000000..b1302b0
    - +  
     1"""
     2Base file upload handler classes, and the built-in concrete subclasses
     3"""
     4import os
     5import tempfile
     6try:
     7    from cStringIO import StringIO
     8except ImportError:
     9    from StringIO import StringIO
     10
     11from django.conf import settings
     12from django.core.exceptions import ImproperlyConfigured
     13from django.core.files.uploadedfile import TemporaryUploadedFile, InMemoryUploadedFile
     14
     15__all__ = ['UploadFileException','StopUpload', 'SkipFile', 'FileUploadHandler',
     16           'TemporaryFileUploadHandler', 'MemoryFileUploadHandler',
     17           'load_handler']
     18
     19class UploadFileException(Exception):
     20    """
     21    Any error having to do with uploading files.
     22    """
     23    pass
     24
     25class StopUpload(UploadFileException):
     26    """
     27    This exception is raised when an upload must abort.
     28    """
     29    def __init__(self, connection_reset=False):
     30        """
     31        If ``connection_reset`` is ``True``, Django knows will halt the upload
     32        without consuming the rest of the upload. This will cause the browser to
     33        show a "connection reset" error.
     34        """
     35        self.connection_reset = connection_reset
     36
     37    def __unicode__(self):
     38        if self.connection_reset:
     39            return u'StopUpload: Halt current upload.'
     40        else:
     41            return u'StopUpload: Consume request data, then halt.'
     42
     43class SkipFile(UploadFileException):
     44    """
     45    This exception is raised by an upload handler that wants to skip a given file.
     46    """
     47    pass
     48
     49class FileUploadHandler(object):
     50    """
     51    Base class for streaming upload handlers.
     52    """
     53    chunk_size = 64 * 2 ** 10 #: The default chunk size is 64 KB.
     54
     55    def __init__(self, request=None):
     56        self.file_name = None
     57        self.content_type = None
     58        self.content_length = None
     59        self.charset = None
     60        self.request = request
     61
     62    def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None):
     63        """
     64        Handle the raw input from the client.
     65
     66        Parameters:
     67
     68            :input_data:
     69                An object that supports reading via .read().
     70            :META:
     71                ``request.META``.
     72            :content_length:
     73                The (integer) value of the Content-Length header from the
     74                client.
     75            :boundary: The boundary from the Content-Type header. Be sure to
     76                prepend two '--'.
     77        """
     78        pass
     79
     80    def new_file(self, field_name, file_name, content_type, content_length, charset=None):
     81        """
     82        Signal that a new file has been started.
     83
     84        Warning: As with any data from the client, you should not trust
     85        content_length (and sometimes won't even get it).
     86        """
     87        self.field_name = field_name
     88        self.file_name = file_name
     89        self.content_type = content_type
     90        self.content_length = content_length
     91        self.charset = charset
     92
     93    def receive_data_chunk(self, raw_data, start):
     94        """
     95        Receive data from the streamed upload parser. ``start`` is the position
     96        in the file of the chunk.
     97        """
     98        raise NotImplementedError()
     99
     100    def file_complete(self, file_size):
     101        """
     102        Signal that a file has completed. File size corresponds to the actual
     103        size accumulated by all the chunks.
     104
     105        Subclasses must should return a valid ``UploadedFile`` object.
     106        """
     107        raise NotImplementedError()
     108
     109    def upload_complete(self):
     110        """
     111        Signal that the upload is complete. Subclasses should perform cleanup
     112        that is necessary for this handler.
     113        """
     114        pass
     115
     116class TemporaryFileUploadHandler(FileUploadHandler):
     117    """
     118    Upload handler that streams data into a temporary file.
     119    """
     120    def __init__(self, *args, **kwargs):
     121        super(TemporaryFileUploadHandler, self).__init__(*args, **kwargs)
     122
     123    def new_file(self, file_name, *args, **kwargs):
     124        """
     125        Create the file object to append to as data is coming in.
     126        """
     127        super(TemporaryFileUploadHandler, self).new_file(file_name, *args, **kwargs)
     128        self.file = TemporaryFile(settings.FILE_UPLOAD_TEMP_DIR)
     129        self.write = self.file.write
     130
     131    def receive_data_chunk(self, raw_data, start):
     132        self.write(raw_data)
     133
     134    def file_complete(self, file_size):
     135        self.file.seek(0)
     136        return TemporaryUploadedFile(self.file, self.file_name,
     137                                     self.content_type, file_size,
     138                                     self.charset)
     139
     140
     141class MemoryFileUploadHandler(FileUploadHandler):
     142    """
     143    File upload handler to stream uploads into memory (used for small files).
     144    """
     145
     146    def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None):
     147        """
     148        Use the content_length to signal whether or not this handler should be in use.
     149        """
     150        # Check the content-length header to see if we should
     151        # If the the post is too large, we cannot use the Memory handler.
     152        if content_length > settings.FILE_UPLOAD_MAX_MEMORY_SIZE:
     153            self.activated = False
     154        else:
     155            self.activated = True
     156
     157    def new_file(self, *args, **kwargs):
     158        super(MemoryFileUploadHandler, self).new_file(*args, **kwargs)
     159        if self.activated:
     160            self.file = StringIO()
     161            return "Stop"
     162
     163    def receive_data_chunk(self, raw_data, start):
     164        """
     165        Add the data to the StringIO file.
     166        """
     167        if self.activated:
     168            self.file.write(raw_data)
     169        else:
     170            return raw_data
     171
     172    def file_complete(self, file_size):
     173        """
     174        Return a file object if we're activated.
     175        """
     176        if not self.activated:
     177            return
     178
     179        return InMemoryUploadedFile(self.file, self.field_name, self.file_name,
     180                                    self.content_type, self.charset, file_size)
     181
     182class TemporaryFile(object):
     183    """
     184    A temporary file that tries to delete itself when garbage collected.
     185    """
     186    def __init__(self, dir):
     187        if not dir:
     188            dir = tempfile.gettempdir()
     189        try:
     190            (fd, name) = tempfile.mkstemp(suffix='.upload', dir=dir)
     191            self.file = os.fdopen(fd, 'w+b')
     192        except (OSError, IOError):
     193            raise OSError("Could not create temporary file for uploading, have you set settings.FILE_UPLOAD_TEMP_DIR correctly?")
     194        self.name = name
     195
     196    def __getattr__(self, name):
     197        a = getattr(self.__dict__['file'], name)
     198        if type(a) != type(0):
     199            setattr(self, name, a)
     200        return a
     201
     202    def __del__(self):
     203        try:
     204            os.unlink(self.name)
     205        except OSError:
     206            pass
     207
     208def load_handler(path, *args, **kwargs):
     209    """
     210    Given a path to a handler, return an instance of that handler.
     211
     212    E.g.::
     213        >>> load_handler('django.core.files.uploadhandler.TemporaryFileUploadHandler', request)
     214        <TemporaryFileUploadHandler object at 0x...>
     215
     216    """
     217    i = path.rfind('.')
     218    module, attr = path[:i], path[i+1:]
     219    try:
     220        mod = __import__(module, {}, {}, [attr])
     221    except ImportError, e:
     222        raise ImproperlyConfigured('Error importing upload handler module %s: "%s"' % (module, e))
     223    except ValueError, e:
     224        raise ImproperlyConfigured('Error importing upload handler module. Is FILE_UPLOAD_HANDLERS a correctly defined list or tuple?')
     225    try:
     226        cls = getattr(mod, attr)
     227    except AttributeError:
     228        raise ImproperlyConfigured('Module "%s" does not define a "%s" upload handler backend' % (module, attr))
     229    return cls(*args, **kwargs)
  • django/core/handlers/modpython.py

    diff --git a/django/core/handlers/modpython.py b/django/core/handlers/modpython.py
    index abab399..332df6f 100644
    a b class ModPythonRequest(http.HttpRequest):  
    5353    def _load_post_and_files(self):
    5454        "Populates self._post and self._files"
    5555        if 'content-type' in self._req.headers_in and self._req.headers_in['content-type'].startswith('multipart'):
    56             self._post, self._files = http.parse_file_upload(self._req.headers_in, self.raw_post_data)
     56            self._raw_post_data = ''
     57            self._post, self._files = self.parse_file_upload(self.META, self._req)
    5758        else:
    5859            self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
    5960
  • django/core/handlers/wsgi.py

    diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py
    index df2ba19..795f139 100644
    a b class WSGIRequest(http.HttpRequest):  
    112112        # Populates self._post and self._files
    113113        if self.method == 'POST':
    114114            if self.environ.get('CONTENT_TYPE', '').startswith('multipart'):
    115                 header_dict = dict([(k, v) for k, v in self.environ.items() if k.startswith('HTTP_')])
    116                 header_dict['Content-Type'] = self.environ.get('CONTENT_TYPE', '')
    117                 self._post, self._files = http.parse_file_upload(header_dict, self.raw_post_data)
     115                self._raw_post_data = ''
     116                self._post, self._files = self.parse_file_upload(self.META, self.environ['wsgi.input'])
    118117            else:
    119118                self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
    120119        else:
  • django/db/models/base.py

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    index 5dd11a9..e1407ca 100644
    a b from django.dispatch import dispatcher  
    1919from django.utils.datastructures import SortedDict
    2020from django.utils.functional import curry
    2121from django.utils.encoding import smart_str, force_unicode, smart_unicode
     22from django.core.files.move import file_move_safe
     23from django.core.files import locks
    2224from django.conf import settings
    2325
    2426try:
    class Model(object):  
    452454    def _get_FIELD_size(self, field):
    453455        return os.path.getsize(self._get_FIELD_filename(field))
    454456
    455     def _save_FIELD_file(self, field, filename, raw_contents, save=True):
     457    def _save_FIELD_file(self, field, filename, raw_field, save=True):
    456458        directory = field.get_directory_name()
    457459        try: # Create the date-based directory if it doesn't exist.
    458460            os.makedirs(os.path.join(settings.MEDIA_ROOT, directory))
    459461        except OSError: # Directory probably already exists.
    460462            pass
     463
     464        #
     465        # Check for old-style usage (files-as-dictionaries). Warn here first
     466        # since there are multiple locations where we need to support both new
     467        # and old usage.
     468        #
     469        if isinstance(raw_field, dict):
     470            import warnings
     471            warnings.warn(
     472                message = "Representing uploaded files as dictionaries is"\
     473                          " deprected. Use django.core.files.SimpleUploadedFile"\
     474                          " instead.",
     475                category = DeprecationWarning,
     476                stacklevel = 2
     477            )
     478            from django.core.files.uploadedfile import SimpleUploadedFile
     479            raw_field = SimpleUploadedFile.from_dict(raw_field)
     480
     481        elif isinstance(raw_field, basestring):
     482            import warnings
     483            warnings.warn(
     484                message = "Representing uploaded files as strings is "\
     485                          " deprecated. Use django.core.files.SimpleUploadedFile "\
     486                          " instead.",
     487                category = DeprecationWarning,
     488                stacklevel = 2
     489            )
     490            from django.core.files.uploadedfile import SimpleUploadedFile
     491            raw_field = SimpleUploadedFile(filename, raw_field)
     492
     493        if filename is None:
     494            filename = raw_field.file_name
     495
    461496        filename = field.get_filename(filename)
    462497
     498        #
    463499        # If the filename already exists, keep adding an underscore to the name of
    464500        # the file until the filename doesn't exist.
     501        #
    465502        while os.path.exists(os.path.join(settings.MEDIA_ROOT, filename)):
    466503            try:
    467504                dot_index = filename.rindex('.')
    class Model(object):  
    469506                filename += '_'
    470507            else:
    471508                filename = filename[:dot_index] + '_' + filename[dot_index:]
     509        #
     510        # Save the file name on the object and write the file to disk
     511        #
    472512
    473         # Write the file to disk.
    474513        setattr(self, field.attname, filename)
    475514
    476515        full_filename = self._get_FIELD_filename(field)
    477         fp = open(full_filename, 'wb')
    478         fp.write(raw_contents)
    479         fp.close()
     516
     517        if hasattr(raw_field, 'temporary_file_path'):
     518            # This file has a file path that we can move.
     519            raw_field.close()
     520            file_move_safe(raw_field.temporary_file_path(), full_filename)
     521
     522        else:
     523            # This is a normal uploadedfile that we can stream.
     524            fp = open(full_filename, 'wb')
     525            locks.lock(fp, locks.LOCK_EX)
     526            for chunk in raw_field.chunk(65535):
     527                fp.write(chunk)
     528            locks.unlock(fp)
     529            fp.close()
    480530
    481531        # Save the width and/or height, if applicable.
    482532        if isinstance(field, ImageField) and (field.width_field or field.height_field):
  • django/db/models/fields/__init__.py

    diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
    index 96bb466..eb4fc59 100644
    a b class FileField(Field):  
    806806        setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self))
    807807        setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self))
    808808        setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self))
    809         setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save))
     809        setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_field, save=True: instance._save_FIELD_file(self, filename, raw_field, save))
    810810        dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls)
    811811
    812812    def delete_file(self, instance):
    class FileField(Field):  
    829829        if new_data.get(upload_field_name, False):
    830830            func = getattr(new_object, 'save_%s_file' % self.name)
    831831            if rel:
    832                 func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"], save)
     832                file = new_data[upload_field_name][0]
    833833            else:
    834                 func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"], save)
     834                file = new_data[upload_field_name]
     835
     836            # Backwards-compatible support for files-as-dictionaries.
     837            # We don't need to raise a warning because Model._save_FIELD_file will
     838            # do so for us.
     839            try:
     840                file_name = file.file_name
     841            except AttributeError:
     842                file_name = file['filename']
     843
     844            func(file_name, file, save)
    835845
    836846    def get_directory_name(self):
    837847        return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to))))
    class FileField(Field):  
    844854    def save_form_data(self, instance, data):
    845855        from django.newforms.fields import UploadedFile
    846856        if data and isinstance(data, UploadedFile):
    847             getattr(instance, "save_%s_file" % self.name)(data.filename, data.content, save=False)
     857            getattr(instance, "save_%s_file" % self.name)(data.filename, data.data, save=False)
    848858
    849859    def formfield(self, **kwargs):
    850860        defaults = {'form_class': forms.FileField}
  • django/http/__init__.py

    diff --git a/django/http/__init__.py b/django/http/__init__.py
    index 7faa3c8..ef15479 100644
    a b try:  
    99except ImportError:
    1010    from cgi import parse_qsl
    1111
    12 from django.utils.datastructures import MultiValueDict, FileDict
     12from django.utils.datastructures import MultiValueDict, ImmutableList
    1313from django.utils.encoding import smart_str, iri_to_uri, force_unicode
    14 
     14from django.http.multipartparser import MultiPartParser
     15from django.conf import settings
     16from django.core.files import uploadhandler
    1517from utils import *
    1618
    1719RESERVED_CHARS="!*'();:@&=+$,/?%#[]"
    1820
    19 
    2021class Http404(Exception):
    2122    pass
    2223
    class HttpRequest(object):  
    2526
    2627    # The encoding used in GET/POST dicts. None means use default setting.
    2728    _encoding = None
     29    _upload_handlers = []
    2830
    2931    def __init__(self):
    3032        self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
    class HttpRequest(object):  
    102104
    103105    encoding = property(_get_encoding, _set_encoding)
    104106
    105 def parse_file_upload(header_dict, post_data):
    106     """Returns a tuple of (POST QueryDict, FILES MultiValueDict)."""
    107     import email, email.Message
    108     from cgi import parse_header
    109     raw_message = '\r\n'.join(['%s:%s' % pair for pair in header_dict.items()])
    110     raw_message += '\r\n\r\n' + post_data
    111     msg = email.message_from_string(raw_message)
    112     POST = QueryDict('', mutable=True)
    113     FILES = MultiValueDict()
    114     for submessage in msg.get_payload():
    115         if submessage and isinstance(submessage, email.Message.Message):
    116             name_dict = parse_header(submessage['Content-Disposition'])[1]
    117             # name_dict is something like {'name': 'file', 'filename': 'test.txt'} for file uploads
    118             # or {'name': 'blah'} for POST fields
    119             # We assume all uploaded files have a 'filename' set.
    120             if 'filename' in name_dict:
    121                 assert type([]) != type(submessage.get_payload()), "Nested MIME messages are not supported"
    122                 if not name_dict['filename'].strip():
    123                     continue
    124                 # IE submits the full path, so trim everything but the basename.
    125                 # (We can't use os.path.basename because that uses the server's
    126                 # directory separator, which may not be the same as the
    127                 # client's one.)
    128                 filename = name_dict['filename'][name_dict['filename'].rfind("\\")+1:]
    129                 FILES.appendlist(name_dict['name'], FileDict({
    130                     'filename': filename,
    131                     'content-type': 'Content-Type' in submessage and submessage['Content-Type'] or None,
    132                     'content': submessage.get_payload(),
    133                 }))
    134             else:
    135                 POST.appendlist(name_dict['name'], submessage.get_payload())
    136     return POST, FILES
    137 
     107    def _initialize_handlers(self):
     108        self._upload_handlers = [uploadhandler.load_handler(handler, self)
     109                                 for handler in settings.FILE_UPLOAD_HANDLERS]
     110
     111    def _set_upload_handlers(self, upload_handlers):
     112        if hasattr(self, '_files'):
     113            raise AttributeError("You cannot set the upload handlers after the upload has been processed.")
     114        self._upload_handlers = upload_handlers
     115
     116    def _get_upload_handlers(self):
     117        if not self._upload_handlers:
     118            # If thre are no upload handlers defined, initialize them from settings.
     119            self._initialize_handlers()
     120        return self._upload_handlers
     121
     122    upload_handlers = property(_get_upload_handlers, _set_upload_handlers)
     123
     124    def parse_file_upload(self, META, post_data):
     125        """Returns a tuple of (POST QueryDict, FILES MultiValueDict)."""
     126        self.upload_handlers = ImmutableList(
     127            self.upload_handlers,
     128            warning = "You cannot alter upload handlers after the upload has been processed."
     129        )
     130        parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding)
     131        return parser.parse()
    138132
    139133class QueryDict(MultiValueDict):
    140134    """
  • new file django/http/multipartparser.py

    diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py
    new file mode 100644
    index 0000000..31b3c23
    - +  
     1"""
     2Multi-part parsing for file uploads.
     3
     4Exposes one class, ``MultiPartParser``, which feeds chunks of uploaded data to
     5file upload handlers for processing.
     6"""
     7import cgi
     8from django.conf import settings
     9from django.utils.datastructures import MultiValueDict
     10from django.utils.encoding import force_unicode
     11from django.utils.text import unescape_entities
     12from django.core.files.uploadhandler import StopUpload, SkipFile
     13
     14__all__ = ('MultiPartParser','MultiPartParserError','InputStreamExhausted')
     15
     16class MultiPartParserError(Exception):
     17    pass
     18
     19class InputStreamExhausted(Exception):
     20    """
     21    No more reads are allowed from this device.
     22    """
     23    pass
     24
     25class FieldType(object):
     26    """
     27    Denotes what type of field a given section of the multipart stream is.
     28
     29    The types can be one of ("FILE", "RAW", "FIELD")
     30    """
     31
     32    def __init__(self, name):
     33        self.name = name
     34
     35    def __str__(self):
     36        return self.name
     37
     38RAW = FieldType('RAW')
     39FILE = FieldType('FILE')
     40FIELD = FieldType('FIELD')
     41
     42class MultiPartParser(object):
     43    """
     44    A rfc2388 multipart/form-data parser.
     45
     46    ``MultiValueDict.parse()`` reads the input stream in ``chunk_size`` chunks
     47    and returns a tuple of ``(MultiValueDict(POST), MultiValueDict(FILES))``. If
     48    ``file_upload_dir`` is defined files will be streamed to temporary files in
     49    that directory.
     50    """
     51    def __init__(self, META, input_data, upload_handlers, encoding=None):
     52        """
     53        Initialize the MultiPartParser object.
     54
     55        :META:
     56            The standard ``META`` dictionary in Django request objects.
     57        :input_data:
     58            The raw post data, as a bytestring.
     59        :upload_handler:
     60            An UploadHandler instance that performs operations on the uploaded
     61            data.
     62        :encoding:
     63            The encoding with which to treat the incoming data.
     64        """
     65
     66        #
     67        # Content-Type should containt multipart and the boundary information.
     68        #
     69
     70        content_type = META.get('HTTP_CONTENT_TYPE', META.get('CONTENT_TYPE', ''))
     71        if not content_type.startswith('multipart/'):
     72            raise MultiPartParserError('Invalid Content-Type: %s' % content_type)
     73
     74        # Parse the header to get the boundary to split the parts.
     75        ctypes, opts = parse_header(content_type)
     76        boundary = opts.get('boundary')
     77        if not boundary or not cgi.valid_boundary(boundary):
     78            raise MultiPartParserError('Invalid boundary in multipart: %s' % boundary)
     79
     80
     81        #
     82        # Content-Length should contain the length of the body we are about
     83        # to receive.
     84        #
     85        try:
     86            content_length = int(META.get('HTTP_CONTENT_LENGTH', META.get('CONTENT_LENGTH',0)))
     87        except (ValueError, TypeError):
     88            # For now set it to 0; we'll try again later on down.
     89            content_length = 0
     90
     91        if content_length <= 0:
     92            # This means we shouldn't continue...raise an error.
     93            raise MultiPartParserError("Invalid content length: %r" % content_length)
     94
     95        self._boundary = boundary
     96        self._input_data = input_data
     97
     98        # For compatibility with low-level network APIs (with 32-bit integers),
     99        # the chunk size should be < 2^31, but still divisible by 4.
     100        self._chunk_size = min(2147483644, *[x.chunk_size for x in upload_handlers if x.chunk_size])
     101
     102        self._meta = META
     103        self._encoding = encoding or settings.DEFAULT_CHARSET
     104        self._content_length = content_length
     105        self._upload_handlers = upload_handlers
     106
     107    def parse(self):
     108        """
     109        Parse the POST data and break it into a FILES MultiValueDict and a POST
     110        MultiValueDict.
     111
     112        Returns a tuple containing the POST and FILES dictionary, respectively.
     113        """
     114        # We have to import QueryDict down here to avoid a circular import.
     115        from django.http import QueryDict
     116
     117        encoding = self._encoding
     118        handlers = self._upload_handlers
     119
     120        limited_input_data = LimitBytes(self._input_data, self._content_length)
     121
     122        # See if the handler will want to take care of the parsing.
     123        # This allows overriding everything if somebody wants it.
     124        for handler in handlers:
     125            result = handler.handle_raw_input(limited_input_data,
     126                                              self._meta,
     127                                              self._content_length,
     128                                              self._boundary,
     129                                              encoding)
     130            if result is not None:
     131                return result[0], result[1]
     132
     133        # Create the data structures to be used later.
     134        self._post = QueryDict('', mutable=True)
     135        self._files = MultiValueDict()
     136
     137        # Instantiate the parser and stream:
     138        stream = LazyStream(ChunkIter(limited_input_data, self._chunk_size))
     139
     140        # Whether or not to signal a file-completion at the beginning of the loop.
     141        old_field_name = None
     142        counters = [0] * len(handlers)
     143
     144        try:
     145            for item_type, meta_data, field_stream in Parser(stream, self._boundary):
     146                if old_field_name:
     147                    # We run this at the beginning of the next loop
     148                    # since we cannot be sure a file is complete until
     149                    # we hit the next boundary/part of the multipart content.
     150                    self.handle_file_complete(old_field_name, counters)
     151
     152                try:
     153                    disposition = meta_data['content-disposition'][1]
     154                    field_name = disposition['name'].strip()
     155                except (KeyError, IndexError, AttributeError):
     156                    continue
     157
     158                transfer_encoding = meta_data.get('content-transfer-encoding')
     159                field_name = force_unicode(field_name, encoding, errors='replace')
     160
     161                if item_type is FIELD:
     162                    # This is a post field, we can just set it in the post
     163                    if transfer_encoding == 'base64':
     164                        raw_data = field_stream.read()
     165                        try:
     166                            data = str(raw_data).decode('base64')
     167                        except:
     168                            data = raw_data
     169                    else:
     170                        data = field_stream.read()
     171
     172                    self._post.appendlist(field_name,
     173                                          force_unicode(data, encoding, errors='replace'))
     174                elif item_type is FILE:
     175                    # This is a file, use the handler...
     176                    file_successful = True
     177                    file_name = disposition.get('filename')
     178                    if not file_name:
     179                        continue
     180                    file_name = force_unicode(file_name, encoding, errors='replace')
     181                    file_name = self.IE_sanitize(unescape_entities(file_name))
     182
     183
     184                    content_type = meta_data.get('content-type', ('',))[0].strip()
     185                    try:
     186                        charset = meta_data.get('content-type', (0,{}))[1].get('charset', None)
     187                    except:
     188                        charset = None
     189
     190                    try:
     191                        content_length = int(meta_data.get('content-length')[0])
     192                    except (IndexError, TypeError, ValueError):
     193                        content_length = None
     194
     195                    counters = [0] * len(handlers)
     196                    try:
     197                        for handler in handlers:
     198                            retval = handler.new_file(field_name, file_name,
     199                                                      content_type, content_length,
     200                                                      charset)
     201                            if retval:
     202                                break
     203
     204                        for chunk in field_stream:
     205                            if transfer_encoding == 'base64':
     206                                # We only special-case base64 transfer encoding
     207                                try:
     208                                    chunk = str(chunk).decode('base64')
     209                                except Exception, e:
     210                                    # Since this is only a chunk, any error is an unfixable error.
     211                                    raise MultiPartParserError("Could not decode base64 data: %r" % e)
     212
     213                            for i, handler in enumerate(handlers):
     214                                chunk_length = len(chunk)
     215                                chunk = handler.receive_data_chunk(chunk,
     216                                                                   counters[i])
     217                                counters[i] += chunk_length
     218                                if chunk is None:
     219                                    # If the chunk received by the handler is None, then don't continue.
     220                                    break
     221
     222                    except SkipFile, e:
     223                        file_successful = False
     224                        # Just use up the rest of this file...
     225                        exhaust(field_stream)
     226                    else:
     227                        # Handle file upload completions on next iteration.
     228                        old_field_name = field_name
     229                else:
     230                    # If this is neither a FIELD or a FILE, just exhaust the stream.
     231                    exhaust(stream)
     232        except StopUpload, e:
     233            if not e.connection_reset:
     234                exhaust(limited_input_data)
     235        else:
     236            # Make sure that the request data is all fed
     237            exhaust(limited_input_data)
     238
     239        # Signal that the upload has completed.
     240        for handler in handlers:
     241            retval = handler.upload_complete()
     242            if retval:
     243                break
     244
     245        return self._post, self._files
     246
     247    def handle_file_complete(self, old_field_name, counters):
     248        """
     249        Handle all the signalling that takes place when a file is complete.
     250        """
     251        for i, handler in enumerate(self._upload_handlers):
     252            file_obj = handler.file_complete(counters[i])
     253            if file_obj:
     254                # If it returns a file object, then set the files dict.
     255                self._files.appendlist(force_unicode(old_field_name,
     256                                                     self._encoding,
     257                                                     errors='replace'),
     258                                       file_obj)
     259                break
     260
     261    def IE_sanitize(self, filename):
     262        """Cleanup filename from Internet Explorer full paths."""
     263        return filename and filename[filename.rfind("\\")+1:].strip()
     264
     265class LazyStream(object):
     266    """
     267    The LazyStream wrapper allows one to get and "unget" bytes from a stream.
     268
     269    Given a producer object (an iterator that yields bytestrings), the
     270    LazyStream object will support iteration, reading, and keeping a "look-back"
     271    variable in case you need to "unget" some bytes.
     272    """
     273    def __init__(self, producer, length=None):
     274        """
     275        Every LazyStream must have a producer when instantiated.
     276
     277        A producer is an iterable that returns a string each time it
     278        is called.
     279        """
     280        self._producer = producer
     281        self._empty = False
     282        self._leftover = ''
     283        self.length = length
     284        self._position = 0
     285        self._remaining = length
     286
     287        # These fields are to do sanity checking to make sure we don't
     288        # have infinite loops getting/ungetting from the stream. The
     289        # purpose overall is to raise an exception if we perform lots
     290        # of stream get/unget gymnastics without getting
     291        # anywhere. Naturally this is not sound, but most probably
     292        # would indicate a bug if the exception is raised.
     293
     294        # largest position tell us how far this lazystream has ever
     295        # been advanced
     296        self._largest_position = 0
     297
     298        # "modifications since" will start at zero and increment every
     299        # time the position is modified but a new largest position is
     300        # not achieved.
     301        self._modifications_since = 0
     302
     303    def tell(self):
     304        return self.position
     305
     306    def read(self, size=None):
     307        def parts():
     308            remaining = (size is not None and [size] or [self._remaining])[0]
     309            # do the whole thing in one shot if no limit was provided.
     310            if remaining is None:
     311                yield ''.join(self)
     312                return
     313
     314            # otherwise do some bookkeeping to return exactly enough
     315            # of the stream and stashing any extra content we get from
     316            # the producer
     317            while remaining != 0:
     318                assert remaining > 0, 'remaining bytes to read should never go negative'
     319
     320                chunk = self.next()
     321
     322                emitting = chunk[:remaining]
     323                self.unget(chunk[remaining:])
     324                remaining -= len(emitting)
     325                yield emitting
     326
     327        out = ''.join(parts())
     328        return out
     329
     330    def next(self):
     331        """
     332        Used when the exact number of bytes to read is unimportant.
     333
     334        This procedure just returns whatever is chunk is conveniently returned
     335        from the iterator instead. Useful to avoid unnecessary bookkeeping if
     336        performance is an issue.
     337        """
     338        if self._leftover:
     339            output = self._leftover
     340            self._leftover = ''
     341        else:
     342            output = self._producer.next()
     343        self.position += len(output)
     344        return output
     345
     346    def close(self):
     347        """
     348        Used to invalidate/disable this lazy stream.
     349
     350        Replaces the producer with an empty list. Any leftover bytes that have
     351        already been read will still be reported upon read() and/or next().
     352        """
     353        self._producer = []
     354
     355    def __iter__(self):
     356        return self
     357
     358    def unget(self, bytes):
     359        """
     360        Places bytes back onto the front of the lazy stream.
     361
     362        Future calls to read() will return those bytes first. The
     363        stream position and thus tell() will be rewound.
     364        """
     365        self.position -= len(bytes)
     366        self._leftover = ''.join([bytes, self._leftover])
     367
     368    def _set_position(self, value):
     369        if value > self._largest_position:
     370            self._modifications_since = 0
     371            self._largest_position = value
     372        else:
     373            self._modifications_since += 1
     374            if self._modifications_since > 500:
     375                raise MultiPartParserError("LazyStream thinks it is somehow stuck. Report this to the Django developers\n" + repr(vars(self)))
     376
     377        self._position = value
     378
     379    position = property(lambda self: self._position, _set_position)
     380
     381class ChunkIter(object):
     382    """
     383    An iterable that will yield chunks of data. Given a file-like object as the
     384    constructor, this object will yield chunks of read operations from that
     385    object.
     386    """
     387    def __init__(self, flo, chunk_size=64 * 1024):
     388        self.flo = flo
     389        self.chunk_size = chunk_size
     390
     391    def next(self):
     392        try:
     393            data = self.flo.read(self.chunk_size)
     394        except InputStreamExhausted:
     395            raise StopIteration()
     396        if data:
     397            return data
     398        else:
     399            raise StopIteration()
     400
     401    def __iter__(self):
     402        return self
     403
     404class LimitBytes(object):
     405    """ Limit bytes for a file object. """
     406    def __init__(self, fileobject, length):
     407        self._file = fileobject
     408        self.remaining = length
     409
     410    def read(self, num_bytes=None):
     411        """
     412        Read data from the underlying file.
     413        If you ask for too much or there isn't anything left,
     414        this will raise an InputStreamExhausted error.
     415        """
     416        if self.remaining <= 0:
     417            raise InputStreamExhausted()
     418        if num_bytes is None:
     419            num_bytes = self.remaining
     420        else:
     421            num_bytes = min(num_bytes, self.remaining)
     422        self.remaining -= num_bytes
     423        return self._file.read(num_bytes)
     424
     425class InterBoundaryIter(object):
     426    """
     427    A Producer that will iterate over boundaries.
     428    """
     429    def __init__(self, stream, boundary):
     430        self._stream = stream
     431        self._boundary = boundary
     432
     433    def __iter__(self):
     434        return self
     435
     436    def next(self):
     437        try:
     438            return LazyStream(BoundaryIter(self._stream, self._boundary))
     439        except InputStreamExhausted:
     440            raise StopIteration()
     441
     442class BoundaryIter(object):
     443    """
     444    A Producer that is sensitive to boundaries.
     445
     446    Will happily yield bytes until a boundary is found. Will yield the bytes
     447    before the boundary, throw away the boundary bytes themselves, and push the
     448    post-boundary bytes back on the stream.
     449
     450    The future calls to .next() after locating the boundary will raise a
     451    StopIteration exception.
     452    """
     453
     454    def __init__(self, stream, boundary):
     455        self._stream = stream
     456        self._boundary = boundary
     457        self._done = False
     458        # rollback an additional six bytes because the format is like
     459        # this: CRLF<boundary>[--CRLF]
     460        self._rollback = len(boundary) + 6
     461
     462        # Try to use mx fast string search if available. Otherwise
     463        # use Python find. Wrap the latter for consistency.
     464        unused_char = self._stream.read(1)
     465        if not unused_char:
     466            raise InputStreamExhausted()
     467        self._stream.unget(unused_char)
     468        try:
     469            from mx.TextTools import FS
     470            self._fs = FS(boundary).find
     471        except ImportError:
     472            self._fs = lambda data: data.find(boundary)
     473
     474    def __iter__(self):
     475        return self
     476
     477    def next(self):
     478        if self._done:
     479            raise StopIteration()
     480
     481        stream = self._stream
     482        rollback = self._rollback
     483
     484        bytes_read = 0
     485        chunks = []
     486        for bytes in stream:
     487            bytes_read += len(bytes)
     488            chunks.append(bytes)
     489            if bytes_read > rollback:
     490                break
     491            if not bytes:
     492                break
     493        else:
     494            self._done = True
     495
     496        if not chunks:
     497            raise StopIteration()
     498
     499        chunk = ''.join(chunks)
     500        boundary = self._find_boundary(chunk, len(chunk) < self._rollback)
     501
     502        if boundary:
     503            end, next = boundary
     504            stream.unget(chunk[next:])
     505            self._done = True
     506            return chunk[:end]
     507        else:
     508            # make sure we dont treat a partial boundary (and
     509            # its separators) as data
     510            if not chunk[:-rollback]:# and len(chunk) >= (len(self._boundary) + 6):
     511                # There's nothing left, we should just return and mark as done.
     512                self._done = True
     513                return chunk
     514            else:
     515                stream.unget(chunk[-rollback:])
     516                return chunk[:-rollback]
     517
     518    def _find_boundary(self, data, eof = False):
     519        """
     520        Finds a multipart boundary in data.
     521
     522        Should no boundry exist in the data None is returned instead. Otherwise
     523        a tuple containing the indices of the following are returned:
     524
     525         * the end of current encapsulation
     526         * the start of the next encapsulation
     527        """
     528        index = self._fs(data)
     529        if index < 0:
     530            return None
     531        else:
     532            end = index
     533            next = index + len(self._boundary)
     534            data_len = len(data) - 1
     535            # backup over CRLF
     536            if data[max(0,end-1)] == '\n':
     537                end -= 1
     538            if data[max(0,end-1)] == '\r':
     539                end -= 1
     540            # skip over --CRLF
     541            #if data[min(data_len,next)] == '-':
     542            #    next += 1
     543            #if data[min(data_len,next)] == '-':
     544            #    next += 1
     545            #if data[min(data_len,next)] == '\r':
     546            #    next += 1
     547            #if data[min(data_len,next)] == '\n':
     548            #    next += 1
     549            return end, next
     550
     551def exhaust(stream_or_iterable):
     552    """
     553    Completely exhausts an iterator or stream.
     554
     555    Raise a MultiPartParserError if the argument is not a stream or an iterable.
     556    """
     557    iterator = None
     558    try:
     559        iterator = iter(stream_or_iterable)
     560    except TypeError:
     561        iterator = ChunkIter(stream_or_iterable, 16384)
     562
     563    if iterator is None:
     564        raise MultiPartParserError('multipartparser.exhaust() was passed a non-iterable or stream parameter')
     565
     566    for __ in iterator:
     567        pass
     568
     569def ParseBoundaryStream(stream, max_header_size):
     570    """
     571    Parses one and exactly one stream that encapsulates a boundary.
     572    """
     573    # Stream at beginning of header, look for end of header
     574    # and parse it if found. The header must fit within one
     575    # chunk.
     576    chunk = stream.read(max_header_size)
     577
     578    # 'find' returns the top of these four bytes, so we'll
     579    # need to munch them later to prevent them from polluting
     580    # the payload.
     581    header_end = chunk.find('\r\n\r\n')
     582
     583    def _parse_header(line):
     584        main_value_pair, params = parse_header(line)
     585        try:
     586            name, value = main_value_pair.split(':', 1)
     587        except:
     588            raise ValueError("Invalid header: %r" % line)
     589        return name, (value, params)
     590
     591    if header_end == -1:
     592        # we find no header, so we just mark this fact and pass on
     593        # the stream verbatim
     594        stream.unget(chunk)
     595        return (RAW, {}, stream)
     596
     597    header = chunk[:header_end]
     598
     599    # here we place any excess chunk back onto the stream, as
     600    # well as throwing away the CRLFCRLF bytes from above.
     601    stream.unget(chunk[header_end + 4:])
     602
     603    TYPE = RAW
     604    outdict = {}
     605
     606    # Eliminate blank lines
     607    for line in header.split('\r\n'):
     608        # This terminology ("main value" and "dictionary of
     609        # parameters") is from the Python docs.
     610        try:
     611            name, (value, params) = _parse_header(line)
     612        except:
     613            continue
     614
     615        if name == 'content-disposition':
     616            TYPE = FIELD
     617            if params.get('filename'):
     618                TYPE = FILE
     619
     620        outdict[name] = value, params
     621
     622    if TYPE == RAW:
     623        stream.unget(chunk)
     624
     625    return (TYPE, outdict, stream)
     626
     627class Parser(object):
     628    def __init__(self, stream, boundary):
     629        self._stream = stream
     630        self._separator = '--' + boundary
     631
     632    def __iter__(self):
     633        boundarystream = InterBoundaryIter(self._stream, self._separator)
     634        for sub_stream in boundarystream:
     635            # Iterate over each part
     636            yield ParseBoundaryStream(sub_stream, 1024)
     637
     638def parse_header(line):
     639    """ Parse the header into a key-value. """
     640    plist = _parse_header_params(';' + line)
     641    key = plist.pop(0).lower()
     642    pdict = {}
     643    for p in plist:
     644        i = p.find('=')
     645        if i >= 0:
     646            name = p[:i].strip().lower()
     647            value = p[i+1:].strip()
     648            if len(value) >= 2 and value[0] == value[-1] == '"':
     649                value = value[1:-1]
     650                value = value.replace('\\\\', '\\').replace('\\"', '"')
     651            pdict[name] = value
     652    return key, pdict
     653
     654def _parse_header_params(s):
     655    plist = []
     656    while s[:1] == ';':
     657        s = s[1:]
     658        end = s.find(';')
     659        while end > 0 and s.count('"', 0, end) % 2:
     660            end = s.find(';', end + 1)
     661        if end < 0:
     662            end = len(s)
     663        f = s[:end]
     664        plist.append(f.strip())
     665        s = s[end:]
     666    return plist
  • django/newforms/fields.py

    diff --git a/django/newforms/fields.py b/django/newforms/fields.py
    index dfe46a2..7d73396 100644
    a b import datetime  
    77import os
    88import re
    99import time
     10try:
     11    from cStringIO import StringIO
     12except ImportError:
     13    from StringIO import StringIO
     14
    1015# Python 2.3 fallbacks
    1116try:
    1217    from decimal import Decimal, DecimalException
    except ImportError:  
    416421
    417422class UploadedFile(StrAndUnicode):
    418423    "A wrapper for files uploaded in a FileField"
    419     def __init__(self, filename, content):
     424    def __init__(self, filename, data):
    420425        self.filename = filename
    421         self.content = content
     426        self.data = data
    422427
    423428    def __unicode__(self):
    424429        """
    class FileField(Field):  
    444449            return None
    445450        elif not data and initial:
    446451            return initial
     452
     453        if isinstance(data, dict):
     454            # We warn once, then support both ways below.
     455            import warnings
     456            warnings.warn(
     457                message = "Representing uploaded files as dictionaries is"\
     458                          " deprecated. Use django.core.files.SimpleUploadedFile "\
     459                          " instead.",
     460                category = DeprecationWarning,
     461                stacklevel = 2
     462            )
     463
    447464        try:
    448             f = UploadedFile(data['filename'], data['content'])
    449         except TypeError:
     465            file_name = data.file_name
     466            file_size = data.file_size
     467        except AttributeError:
     468            try:
     469                file_name = data.get('filename')
     470                file_size = bool(data['content'])
     471            except (AttributeError, KeyError):
     472                raise ValidationError(self.error_messages['invalid'])
     473
     474        if not file_name:
    450475            raise ValidationError(self.error_messages['invalid'])
    451         except KeyError:
    452             raise ValidationError(self.error_messages['missing'])
    453         if not f.content:
     476        if not file_size:
    454477            raise ValidationError(self.error_messages['empty'])
    455         return f
     478
     479        return UploadedFile(file_name, data)
    456480
    457481class ImageField(FileField):
    458482    default_error_messages = {
    class ImageField(FileField):  
    470494        elif not data and initial:
    471495            return initial
    472496        from PIL import Image
    473         from cStringIO import StringIO
     497
     498        # We need to get a file object for PIL. We might have a path or we might
     499        # have to read the data into memory.
     500        if hasattr(data, 'temporary_file_path'):
     501            file = data.temporary_file_path()
     502        else:
     503            if hasattr(data, 'read'):
     504                file = StringIO(data.read())
     505            else:
     506                file = StringIO(data['content'])
     507
    474508        try:
    475509            # load() is the only method that can spot a truncated JPEG,
    476510            #  but it cannot be called sanely after verify()
    477             trial_image = Image.open(StringIO(f.content))
     511            trial_image = Image.open(file)
    478512            trial_image.load()
     513
     514            # Since we're about to use the file again we have to reset the
     515            # file object if possible.
     516            if hasattr(file, 'reset'):
     517                file.reset()
     518
    479519            # verify() is the only method that can spot a corrupt PNG,
    480520            #  but it must be called immediately after the constructor
    481             trial_image = Image.open(StringIO(f.content))
     521            trial_image = Image.open(file)
    482522            trial_image.verify()
    483523        except Exception: # Python Imaging Library doesn't recognize it as an image
    484524            raise ValidationError(self.error_messages['invalid_image'])
  • django/oldforms/__init__.py

    diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py
    index fc87271..ee838d2 100644
    a b class FileUploadField(FormField):  
    680680        self.field_name, self.is_required = field_name, is_required
    681681        self.validator_list = [self.isNonEmptyFile] + validator_list
    682682
    683     def isNonEmptyFile(self, field_data, all_data):
     683    def isNonEmptyFile(self, new_data, all_data):
     684        if hasattr(new_data, 'upload_errors'):
     685            upload_errors = new_data.upload_errors()
     686            if upload_errors:
     687                raise validators.CriticalValidationError, upload_errors
    684688        try:
    685             content = field_data['content']
    686         except TypeError:
    687             raise validators.CriticalValidationError, ugettext("No file was submitted. Check the encoding type on the form.")
    688         if not content:
     689            file_size = new_data.file_size
     690        except AttributeError:
     691            file_size = len(new_data['content'])
     692        if not file_size:
    689693            raise validators.CriticalValidationError, ugettext("The submitted file is empty.")
    690694
    691695    def render(self, data):
    692696        return mark_safe(u'<input type="file" id="%s" class="v%s" name="%s" />' % \
    693697            (self.get_id(), self.__class__.__name__, self.field_name))
    694698
     699    def prepare(self, new_data):
     700        if hasattr(new_data, 'upload_errors'):
     701            upload_errors = new_data.upload_errors()
     702            new_data[self.field_name] = { '_file_upload_error': upload_errors }
     703
    695704    def html2python(data):
    696705        if data is None:
    697706            raise EmptyValue
  • django/test/client.py

    diff --git a/django/test/client.py b/django/test/client.py
    index a15876e..6313181 100644
    a b  
    11import urllib
    22import sys
    33import os
    4 from cStringIO import StringIO
     4try:
     5    from cStringIO import StringIO
     6except ImportError:
     7    from StringIO import StringIO
    58from django.conf import settings
    69from django.contrib.auth import authenticate, login
    710from django.core.handlers.base import BaseHandler
    from django.utils.itercompat import is_iterable  
    1922BOUNDARY = 'BoUnDaRyStRiNg'
    2023MULTIPART_CONTENT = 'multipart/form-data; boundary=%s' % BOUNDARY
    2124
     25class FakePayload(object):
     26    """
     27    A wrapper around StringIO that restricts what can be read since data from
     28    the network can't be seeked and cannot be read outside of its content
     29    length. This makes sure that views can't do anything under the test client
     30    that wouldn't work in Real Life.
     31    """
     32    def __init__(self, content):
     33        self.__content = StringIO(content)
     34        self.__len = len(content)
     35
     36    def read(self, num_bytes=None):
     37        if num_bytes is None:
     38            num_bytes = self.__len or 1
     39        assert self.__len >= num_bytes, "Cannot read more than the available bytes from the HTTP incoming data."
     40        content = self.__content.read(num_bytes)
     41        self.__len -= num_bytes
     42        return content
     43
    2244class ClientHandler(BaseHandler):
    2345    """
    2446    A HTTP Handler that can be used for testing purposes.
    class Client:  
    236258            'CONTENT_TYPE':   content_type,
    237259            'PATH_INFO':      urllib.unquote(path),
    238260            'REQUEST_METHOD': 'POST',
    239             'wsgi.input':     StringIO(post_data),
     261            'wsgi.input':     FakePayload(post_data),
    240262        }
    241263        r.update(extra)
    242264
  • django/utils/datastructures.py

    diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py
    index 4c278c0..7c8e246 100644
    a b class DotExpandedDict(dict):  
    332332            except TypeError: # Special-case if current isn't a dict.
    333333                current = {bits[-1]: v}
    334334
    335 class FileDict(dict):
     335class ImmutableList(tuple):
    336336    """
    337     A dictionary used to hold uploaded file contents. The only special feature
    338     here is that repr() of this object won't dump the entire contents of the
    339     file to the output. A handy safeguard for a large file upload.
     337    A tuple-like object that raises useful errors when it is asked to mutate.
     338
     339    Example::
     340
     341        >>> a = ImmutableList(range(5), warning="You cannot mutate this.")
     342        >>> a[3] = '4'
     343        Traceback (most recent call last):
     344            ...
     345        AttributeError: You cannot mutate this.
    340346    """
    341     def __repr__(self):
    342         if 'content' in self:
    343             d = dict(self, content='<omitted>')
    344             return dict.__repr__(d)
    345         return dict.__repr__(self)
     347
     348    def __new__(cls, *args, **kwargs):
     349        if 'warning' in kwargs:
     350            warning = kwargs['warning']
     351            del kwargs['warning']
     352        else:
     353            warning = 'ImmutableList object is immutable.'
     354        self = tuple.__new__(cls, *args, **kwargs)
     355        self.warning = warning
     356        return self
     357
     358    def complain(self, *wargs, **kwargs):
     359        if isinstance(self.warning, Exception):
     360            raise self.warning
     361        else:
     362            raise AttributeError, self.warning
     363
     364    # All list mutation functions complain.
     365    __delitem__  = complain
     366    __delslice__ = complain
     367    __iadd__     = complain
     368    __imul__     = complain
     369    __setitem__  = complain
     370    __setslice__ = complain
     371    append       = complain
     372    extend       = complain
     373    insert       = complain
     374    pop          = complain
     375    remove       = complain
     376    sort         = complain
     377    reverse      = complain
     378 No newline at end of file
  • django/utils/text.py

    diff --git a/django/utils/text.py b/django/utils/text.py
    index aa190c8..3686a45 100644
    a b from django.conf import settings  
    33from django.utils.encoding import force_unicode
    44from django.utils.functional import allow_lazy
    55from django.utils.translation import ugettext_lazy
     6from htmlentitydefs import name2codepoint
    67
    78# Capitalizes the first letter of a string.
    89capfirst = lambda x: x and force_unicode(x)[0].upper() + force_unicode(x)[1:]
    def smart_split(text):  
    222223            yield bit
    223224smart_split = allow_lazy(smart_split, unicode)
    224225
     226def _replace_entity(match):
     227     text = match.group(1)
     228     if text[0] == u'#':
     229         text = text[1:]
     230         try:
     231             if text[0] in u'xX':
     232                 c = int(text[1:], 16)
     233             else:
     234                 c = int(text)
     235             return unichr(c)
     236         except ValueError:
     237             return match.group(0)
     238     else:
     239         try:
     240             return unichr(name2codepoint[text])
     241         except (ValueError, KeyError):
     242             return match.group(0)
     243
     244_entity_re = re.compile(r"&(#?[xX]?(?:[0-9a-fA-F]+|\w{1,8}));")
     245
     246def unescape_entities(text):
     247     return _entity_re.sub(_replace_entity, text)
     248unescape_entities = allow_lazy(unescape_entities, unicode)
  • docs/newforms.txt

    diff --git a/docs/newforms.txt b/docs/newforms.txt
    index 04e4c1a..296fc04 100644
    a b ContactForm to include an ``ImageField`` called ``mugshot``, we  
    805805need to bind the file data containing the mugshot image::
    806806
    807807    # Bound form with an image field
     808    >>> from django.core.files.uploadedfile import SimpleUploadedFile
    808809    >>> data = {'subject': 'hello',
    809810    ...         'message': 'Hi there',
    810811    ...         'sender': 'foo@example.com',
    811812    ...         'cc_myself': True}
    812     >>> file_data = {'mugshot': {'filename':'face.jpg'
    813     ...                          'content': <file data>}}
     813    >>> file_data = {'mugshot': SimpleUploadedFile('face.jpg', <file data>)}
    814814    >>> f = ContactFormWithMugshot(data, file_data)
    815815
    816816In practice, you will usually specify ``request.FILES`` as the source
  • docs/request_response.txt

    diff --git a/docs/request_response.txt b/docs/request_response.txt
    index 866a697..2d2010a 100644
    a b All attributes except ``session`` should be considered read-only.  
    8080    strings.
    8181
    8282``FILES``
     83   **New in Django development version**
    8384    A dictionary-like object containing all uploaded files. Each key in
    8485    ``FILES`` is the ``name`` from the ``<input type="file" name="" />``. Each
    85     value in ``FILES`` is a standard Python dictionary with the following three
    86     keys:
     86    value in ``FILES`` is an ``UploadedFile`` object containing at least the
     87    following attributes:
     88
     89        * ``read(num_bytes=None)`` -- Read a number of bytes from the file.
     90        * ``file_name`` -- The name of the uploaded file.
     91        * ``file_size`` -- The size, in bytes, of the uploaded file.
     92        * ``chunk()`` -- A generator that yields sequential chunks of data.
    8793
    88         * ``filename`` -- The name of the uploaded file, as a Python string.
    89         * ``content-type`` -- The content type of the uploaded file.
    90         * ``content`` -- The raw content of the uploaded file.
     94    See `File Uploads`_ for more information. Note that ``FILES`` will only
     95    contain data if the request method was POST and the ``<form>`` that posted
     96    to the request had ``enctype="multipart/form-data"``. Otherwise, ``FILES``
     97    will be a blank dictionary-like object.
    9198
    92     Note that ``FILES`` will only contain data if the request method was POST
    93     and the ``<form>`` that posted to the request had
    94     ``enctype="multipart/form-data"``. Otherwise, ``FILES`` will be a blank
    95     dictionary-like object.
     99    .. _File Uploads: ../upload_handling/
    96100
    97101``META``
    98102    A standard Python dictionary containing all available HTTP headers.
  • docs/settings.txt

    diff --git a/docs/settings.txt b/docs/settings.txt
    index a1c8c74..1460f00 100644
    a b Default: ``''`` (Empty string)  
    279279
    280280The database backend to use. The build-in database backends are
    281281``'postgresql_psycopg2'``, ``'postgresql'``, ``'mysql'``, ``'mysql_old'``,
    282 ``'sqlite3'`` and ``'oracle'``.
     282``'sqlite3'``, ``'oracle'``, and ``'oracle'``.
    283283
    284284In the Django development version, you can use a database backend that doesn't
    285285ship with Django by setting ``DATABASE_ENGINE`` to a fully-qualified path (i.e.
    Default: ``'utf-8'``  
    525525The character encoding used to decode any files read from disk. This includes
    526526template files and initial SQL data files.
    527527
     528FILE_UPLOAD_HANDLERS
     529--------------------
     530
     531**New in Django development version**
     532
     533Default::
     534
     535    ("django.core.files.fileuploadhandler.MemoryFileUploadHandler",
     536     "django.core.files.fileuploadhandler.TemporaryFileUploadHandler",)
     537
     538A tuple of handlers to use for uploading.
     539
     540FILE_UPLOAD_MAX_MEMORY_SIZE
     541---------------------------
     542
     543**New in Django development version**
     544
     545Default: ``2621440`` (i.e. 2.5 MB).
     546
     547The maximum size (in bytes) that an upload will be before it gets streamed to the file system.
     548
     549FILE_UPLOAD_TEMP_DIR
     550--------------------
     551
     552**New in Django development version**
     553
     554Default: ``None``
     555
     556The directory to store data temporarily while uploading files. If ``None``, Django will use the standard temporary directory for the operating system. For example, this will default to '/tmp' on *nix-style operating systems.
     557
    528558FIXTURE_DIRS
    529559-------------
    530560
  • new file docs/upload_handling.txt

    diff --git a/docs/upload_handling.txt b/docs/upload_handling.txt
    new file mode 100644
    index 0000000..28c7dbe
    - +  
     1============
     2File Uploads
     3============
     4
     5**New in Django development version**
     6
     7Most Web sites wouldn't be complete without a way to upload files. Before the
     8file even gets into ``request.FILES`` Django has to decide where to put the
     9all the incoming data. This document describes how Django takes the incoming
     10request and populates ``request.FILES``.
     11
     12.. _Request and response objects: ../request_response/#attributes
     13
     14Default Behavior
     15================
     16
     17After setting up a quick model that contains a `FileField`_ and enabling the
     18`Admin interface`_ you can quickly use a form to upload files to your site.
     19By default, if the upload is smaller than **2.5 Megabytes** in size, Django
     20will hold the entire contents of the upload in memory, and the file will
     21quickly be saved to its final destination (as defined by
     22`settings.MEDIA_ROOT`_) without any intermediary.
     23
     24If the entire upload is larger than 2.5 Megabytes, then it will -- by
     25default -- write the contents of the uploaded file to a temporary file
     26in your operating system's default temporary directory. On a posix platform,
     27this means that you could expect Django to generate a file similar in name
     28to ``/tmp/tmpzfp6I6.upload``. During the upload, you may notice this file
     29grow in size as Django loads the data onto disk.
     30
     31.. note::
     32    You may find that the default temporary directory is not writable.
     33    This is especially true if you are on shared hosting. If this is
     34    the case, Django will raise an error when you try to upload. Please
     35    read below in `Extending the Default`_ to change this directory.
     36
     37
     38.. _FileField: ../model-api/#filefield
     39.. _Admin interface: ../tutorial02/#activate-the-admin-site
     40.. _settings.MEDIA_ROOT: ../settings/#media-root
     41
     42Extending the Default
     43=====================
     44
     45Suppose you have quite a bit of memory. You may decide that you want Django
     46to stream only if the entire upload exceeds 10 Megabytes. You may even
     47decide that whatever directory is the platform default is not suitable
     48for your project. If this is the case, Django provides these two settings:
     49
     50    =============================== ======================================
     51    Setting                         Description
     52    =============================== ======================================
     53    ``FILE_UPLOAD_MAX_MEMORY_SIZE`` The maximum size of a request in bytes
     54                                    for which Django will try to load the
     55                                    entire upload contents in memory.
     56
     57    ``FILE_UPLOAD_TEMP_DIR``        The directory on the file system where
     58                                    uploaded contents will be temporarily
     59                                    stored if not completely in memory.
     60                                    E.g.: ``"/tmp"``
     61    =============================== ======================================
     62
     63There is one final setting -- ``FILE_UPLOAD_HANDLERS`` -- which allows complete
     64customization of the upload process.
     65
     66Upload Handlers
     67===============
     68
     69Through upload handlers Django provides the flexibility to extend the
     70upload process beyond simple storage. You can use custom handlers to enforce
     71user-level quotas, compress data on the fly, render progress bars, and
     72even send data to another warehouse directly without storing it locally.
     73
     74There are two pieces to the Django upload handling: the upload handler and the
     75uploaded file. These are both represented by python classes --
     76``FileUploadHandler`` and ``UploadedFile`` respectively. Throughout the lifetime
     77of the upload process, Django will call on the upload handler to handle the
     78upload, while the upload handler is expected to provide Django an ``UploadedFile``
     79object to successfully complete a file's upload.
     80
     81Setting Default Upload Handlers for your Project
     82------------------------------------------------
     83
     84Similar to `Middleware`_, upload handlers have an order that is initially
     85defined in ``settings.FILE_UPLOAD_HANDLERS``. The default value is::
     86
     87    ("django.core.files.fileuploadhandler.MemoryFileUploadHandler",
     88     "django.core.files.fileuploadhandler.TemporaryFileUploadHandler",)
     89
     90This literally means: Try putting the upload in memory first and failing
     91that put the upload in a temporary file.
     92
     93This behavior, however, is completely dependent on how each of those handlers
     94is written. For example, if someone gave you a ``Rot13UploadHandler``, you can
     95update your ``settings`` to contain::
     96
     97   FILE_UPLOAD_HANDLERS = (
     98     "app.uploadhandlers.rot13.Rot13UploadHandler",
     99     "django.core.files.fileuploadhandler.MemoryFileUploadHandler",
     100     "django.core.files.fileuploadhandler.TemporaryFileUploadHandler",
     101   )
     102
     103And the ``Rot13UploadHandler`` will perform the ``rot13`` operation on all
     104data before the data get to subsequent handlers.
     105
     106.. _Middleware: ../middleware/
     107
     108Modifying your Upload Handlers Dynamically
     109------------------------------------------
     110
     111During the lifetime of your project, you may realize that a particular
     112view or views require different uploading behavior. For this reason,
     113the ``request`` object contains a list of handlers
     114(``request.upload_handlers``) that will be called in order. To append
     115a handler to the list, you would append to it like any other list.
     116For example, suppose you had an ``ProgressBarUploadHandler`` class.
     117To append it to your upload handlers you would write::
     118
     119    request.upload_handlers.append(ProgressBarUploadHandler())
     120
     121However, since the progress bar handler would probably need to run before the
     122other handlers get a chance, you'd probably want to insert it. That is, you'd
     123write::
     124
     125   request.upload_handlers.insert(0, ProgressBarUploadHandler())
     126
     127If you want to replace the upload handlers completely, you can just assign a new
     128list::
     129
     130   request.upload_handlers = [ProgressBarUploadHandler()]
     131
     132And all Django will do is keep a progress, but do nothing with the file itself!
     133One more note: After the upload has completed, you are no longer allowed to
     134assign or modify this list, and Django will raise an error if you try to modify
     135this list after an upload.
     136
     137Writing a File Upload Handler
     138-----------------------------
     139
     140All file upload handlers are subclasses of ``FileUploadHandler``, found in
     141``django.core.files.fileuploadhandler``. To create your handler, you need to
     142define the required methods followed by the methods that make most sense to you:
     143
     144chunk_size
     145~~~~~~~~~~
     146
     147An integer attribute that specifies what sized chunks Django should store
     148into memory and feed into the handler. The chunk sizes should be divisible by
     149``4`` and should not exceed ``2 ** 31 - 1`` in size. When there are multiple
     150chunk sizes provided by multiple handlers, Django will use the smallest chunk
     151size.
     152
     153__init__
     154~~~~~~~~
     155
     156The constructor, which should optionally support taking a request object as its
     157first argument. When called via ``settings.FILE_UPLOAD_HANDLERS``, it will be
     158passed a request object.
     159
     160Interface: ``__init__(self, request=None)``
     161
     162``request`` is the request object. Do not access ``request.POST`` or
     163``request.FILES``.
     164
     165new_file
     166~~~~~~~~
     167
     168``new_file`` signals that a new file is starting. You can initialize a file or
     169whatever else is needed on a new file upload.
     170
     171Interface: ``new_file(self, field_name, file_name, content_type, content_length,
     172charset)``
     173
     174``field_name`` is a string name of the field this was POSTed as.
     175
     176``file_name`` is the unicode filename that was provided by the browser.
     177
     178``content_type`` is the MIME type provided by the browser -- E.g.
     179``'image/jpeg'``.
     180
     181``content_length`` is the length of the image given by the browser if provided,
     182``None`` otherwise.
     183
     184``charset`` is the charset given by the browser if provided, ``None`` otherwise.
     185
     186Returns: ``None`` if you want other handlers to get ``new_file`` called.
     187Something nonzero if you don't want subsequent handlers to get a chance.
     188
     189receive_data_chunk
     190~~~~~~~~~~~~~~~~~~
     191*required*
     192
     193Receives a segment of data from the file upload. For example: The
     194``TemporaryFileUploadHandler`` takes the data and writes it to disk.
     195
     196Interface: ``receive_data_chunk(self, raw_data, start)``
     197
     198``raw_data`` is a byte string containing the uploaded data.
     199
     200``start`` is the position in the file where this ``raw_data`` chunk begins.
     201
     202Returns: ``None`` if you don't want the subsequent upload handlers to receive
     203the data. Whatever else you return gets fed into the subsequent upload handlers'
     204``receive_data_chunk`` method. In this way, one handler can be a "filter" for
     205other handlers.
     206
     207Exceptions: If you raise a ``StopUpload`` or a ``SkipFile`` exception, the
     208upload will abort or the file will be skipped respectively.
     209
     210file_complete
     211~~~~~~~~~~~~~
     212*required*
     213
     214Signals that a file has finished uploading. Expected to return an
     215``UploadedFile`` object if the file has been packaged successfully.
     216
     217Interface: ``file_complete(self, file_size)``
     218
     219``file_size`` is the number of bytes you have received for this file.
     220
     221Returns: An ``UploadedFile`` object to set in the ``request.FILES`` dictionary.
     222``None`` if you want the subsequent upload handlers to get an ``UploadedFile``
     223object.
     224
     225upload_complete
     226~~~~~~~~~~~~~~~
     227
     228Defines when the entire upload has completed.
     229
     230Interface: ``upload_complete(self)``
     231
     232handle_raw_input
     233~~~~~~~~~~~~~~~~
     234
     235Allows the handler to completely override the parsing of the raw HTTP-layered
     236input.
     237
     238Interface: ``handle_raw_input(self, input_data, META, content_length, boundary,
     239encoding)``
     240
     241``input_data`` is a file-like object that supports the read operation.
     242
     243``META`` is the same object as ``request.META``.
     244
     245``content_length`` is the length of the data in ``input_data``. Don't read more
     246than ``content_length`` bytes from ``input_data``.
     247
     248``boundary`` is the MIME boundary for this request.
     249
     250``encoding`` is the encoding of the request.
     251
     252Returns: ``None`` if you want to go on to the next stage, ``(POST, FILES)`` if
     253you want to return the new data structures suitable for the request directly.
     254
     255Defining an Uploaded File
     256-------------------------
     257
     258All file upload handlers are subclasses of ``UploadedFile``, found in
     259``django.core.files.uploadedfile``. The uploaded file object is returned by the
     260handler above in ``file_complete``. To create your own uploaded file class, you
     261need to define the required methods followed by the methods that make most sense
     262to you:
     263
     264read
     265~~~~
     266*required*
     267
     268Interface: ``read(self, num_bytes=None)``
     269
     270Returns: A byte string of length ``num_bytes`` (or the size of the file if
     271``num_bytes`` is not supplied).
     272
     273chunk
     274~~~~~
     275
     276A generator to yield small chunks from the file. With the ``read()`` defined,
     277the ``UploadedFile`` class already defines a ``chunk()`` method that's probably
     278suitable.
     279
     280Interface: ``chunk(self, chunk_size=None)``
     281
     282multiple_chunks
     283~~~~~~~~~~~~~~~
     284
     285Interface: ``multiple_chunks(self, chunk_size=None)``
     286
     287Returns: ``True`` or ``False`` depending on whether or not the user of this
     288file can expect more than one chunk when calling ``chunk(self, chunk_size)``.
     289If all the data is in memory, you should return ``False``.
     290
     291temporary_file_path
     292~~~~~~~~~~~~~~~~~~~
     293
     294If defined, this method should return the file path of the file on the
     295operating system. This will let users move files rather than write to
     296new files if the option is available.
     297
     298Interface: ``temporary_file_path(self)``
     299
     300Returns: A file path in a file system on the local computer.
     301
     302upload_errors
     303~~~~~~~~~~~~~
     304
     305If defined, this method should return a string describing errors that
     306occured during uploads.
     307
     308Interface: ``upload_errors(self)``
     309
     310Returns: A ``unicode`` string describing an error that occured with the
     311file's upload.
  • tests/modeltests/model_forms/models.py

    diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
    index 470312f..0ad5686 100644
    a b class TextFile(models.Model):  
    6767
    6868class ImageFile(models.Model):
    6969    description = models.CharField(max_length=20)
    70     image = models.FileField(upload_to=tempfile.gettempdir())
     70    try:
     71        # If PIL is available, try testing PIL.
     72        # Otherwise, it's equivalent to TextFile above.
     73        import Image
     74        image = models.ImageField(upload_to=tempfile.gettempdir())
     75    except ImportError:
     76        image = models.FileField(upload_to=tempfile.gettempdir())
    7177
    7278    def __unicode__(self):
    7379        return self.description
    class ImageFile(models.Model):  
    7581__test__ = {'API_TESTS': """
    7682>>> from django import newforms as forms
    7783>>> from django.newforms.models import ModelForm
     84>>> from django.core.files.uploadedfile import SimpleUploadedFile
     85>>> from warnings import filterwarnings
     86>>> filterwarnings("ignore")
    7887
    7988The bare bones, absolutely nothing custom, basic case.
    8089
    False  
    792801
    793802# Upload a file and ensure it all works as expected.
    794803
     804>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test1.txt', 'hello world')})
     805>>> f.is_valid()
     806True
     807>>> type(f.cleaned_data['file'])
     808<class 'django.newforms.fields.UploadedFile'>
     809>>> instance = f.save()
     810>>> instance.file
     811u'...test1.txt'
     812
     813>>> os.unlink(instance.get_file_filename())
     814
    795815>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test1.txt', 'content': 'hello world'}})
    796816>>> f.is_valid()
    797817True
    u'...test1.txt'  
    814834u'...test1.txt'
    815835
    816836# Delete the current file since this is not done by Django.
    817 
    818837>>> os.unlink(instance.get_file_filename())
    819838
    820839# Override the file by uploading a new one.
    821840
    822 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test2.txt', 'content': 'hello world'}}, instance=instance)
     841>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test2.txt', 'hello world')}, instance=instance)
    823842>>> f.is_valid()
    824843True
    825844>>> instance = f.save()
    826845>>> instance.file
    827846u'...test2.txt'
    828847
     848# Delete the current file since this is not done by Django.
     849>>> os.unlink(instance.get_file_filename())
     850
     851>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test2.txt', 'content': 'hello world'}})
     852>>> f.is_valid()
     853True
     854>>> instance = f.save()
     855>>> instance.file
     856u'...test2.txt'
     857
     858# Delete the current file since this is not done by Django.
     859>>> os.unlink(instance.get_file_filename())
     860
    829861>>> instance.delete()
    830862
    831863# Test the non-required FileField
    True  
    838870>>> instance.file
    839871''
    840872
    841 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test3.txt', 'content': 'hello world'}}, instance=instance)
     873>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')}, instance=instance)
    842874>>> f.is_valid()
    843875True
    844876>>> instance = f.save()
    845877>>> instance.file
    846878u'...test3.txt'
     879
     880# Delete the current file since this is not done by Django.
     881>>> os.unlink(instance.get_file_filename())
     882>>> instance.delete()
     883
     884>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test3.txt', 'content': 'hello world'}})
     885>>> f.is_valid()
     886True
     887>>> instance = f.save()
     888>>> instance.file
     889u'...test3.txt'
     890
     891# Delete the current file since this is not done by Django.
     892>>> os.unlink(instance.get_file_filename())
    847893>>> instance.delete()
    848894
    849895# ImageField ###################################################################
    u'...test3.txt'  
    858904
    859905>>> image_data = open(os.path.join(os.path.dirname(__file__), "test.png")).read()
    860906
     907>>> f = ImageFileForm(data={'description': u'An image'}, files={'image': SimpleUploadedFile('test.png', image_data)})
     908>>> f.is_valid()
     909True
     910>>> type(f.cleaned_data['image'])
     911<class 'django.newforms.fields.UploadedFile'>
     912>>> instance = f.save()
     913>>> instance.image
     914u'...test.png'
     915
     916# Delete the current file since this is not done by Django.
     917>>> os.unlink(instance.get_image_filename())
     918
    861919>>> f = ImageFileForm(data={'description': u'An image'}, files={'image': {'filename': 'test.png', 'content': image_data}})
    862920>>> f.is_valid()
    863921True
    u'...test.png'  
    885943
    886944# Override the file by uploading a new one.
    887945
    888 >>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': {'filename': 'test2.png', 'content': image_data}}, instance=instance)
     946>>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': SimpleUploadedFile('test2.png', image_data)}, instance=instance)
     947>>> f.is_valid()
     948True
     949>>> instance = f.save()
     950>>> instance.image
     951u'...test2.png'
     952
     953# Delete the current file since this is not done by Django.
     954>>> os.unlink(instance.get_image_filename())
     955>>> instance.delete()
     956
     957>>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': {'filename': 'test2.png', 'content': image_data}})
    889958>>> f.is_valid()
    890959True
    891960>>> instance = f.save()
    892961>>> instance.image
    893962u'...test2.png'
    894963
     964# Delete the current file since this is not done by Django.
     965>>> os.unlink(instance.get_image_filename())
    895966>>> instance.delete()
    896967
    897968# Test the non-required ImageField
    True  
    904975>>> instance.image
    905976''
    906977
    907 >>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': {'filename': 'test3.png', 'content': image_data}}, instance=instance)
     978>>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': SimpleUploadedFile('test3.png', image_data)}, instance=instance)
     979>>> f.is_valid()
     980True
     981>>> instance = f.save()
     982>>> instance.image
     983u'...test3.png'
     984
     985# Delete the current file since this is not done by Django.
     986>>> os.unlink(instance.get_image_filename())
     987>>> instance.delete()
     988
     989>>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': {'filename': 'test3.png', 'content': image_data}})
    908990>>> f.is_valid()
    909991True
    910992>>> instance = f.save()
  • tests/regressiontests/bug639/tests.py

    diff --git a/tests/regressiontests/bug639/tests.py b/tests/regressiontests/bug639/tests.py
    index f9596d0..2726dec 100644
    a b import unittest  
    99from regressiontests.bug639.models import Photo
    1010from django.http import QueryDict
    1111from django.utils.datastructures import MultiValueDict
     12from django.core.files.uploadedfile import SimpleUploadedFile
    1213
    1314class Bug639Test(unittest.TestCase):
    1415       
    class Bug639Test(unittest.TestCase):  
    2122       
    2223        # Fake a request query dict with the file
    2324        qd = QueryDict("title=Testing&image=", mutable=True)
    24         qd["image_file"] = {
    25             "filename" : "test.jpg",
    26             "content-type" : "image/jpeg",
    27             "content" : img
    28         }
    29        
     25        qd["image_file"] = SimpleUploadedFile('test.jpg', img, 'image/jpeg')
     26
    3027        manip = Photo.AddManipulator()
    3128        manip.do_html2python(qd)
    3229        p = manip.save(qd)
    class Bug639Test(unittest.TestCase):  
    3936        Make sure to delete the "uploaded" file to avoid clogging /tmp.
    4037        """
    4138        p = Photo.objects.get()
    42         os.unlink(p.get_image_filename())
    43  No newline at end of file
     39        os.unlink(p.get_image_filename())
  • tests/regressiontests/datastructures/tests.py

    diff --git a/tests/regressiontests/datastructures/tests.py b/tests/regressiontests/datastructures/tests.py
    index b51b4b1..1d6a846 100644
    a b Init from sequence of tuples  
    117117>>> d['person']['2']['firstname']
    118118['Adrian']
    119119
    120 ### FileDict ################################################################
    121 
    122 >>> d = FileDict({'content': 'once upon a time...'})
    123 >>> repr(d)
    124 "{'content': '<omitted>'}"
    125 >>> d = FileDict({'other-key': 'once upon a time...'})
     120### ImmutableList ################################################################
     121>>> d = ImmutableList(range(10))
     122>>> d.sort()
     123Traceback (most recent call last):
     124  File "<stdin>", line 1, in <module>
     125  File "/var/lib/python-support/python2.5/django/utils/datastructures.py", line 359, in complain
     126    raise AttributeError, self.warning
     127AttributeError: ImmutableList object is immutable.
    126128>>> repr(d)
    127 "{'other-key': 'once upon a time...'}"
     129'(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)'
     130>>> d = ImmutableList(range(10), warning="Object is immutable!")
     131>>> d[1]
     1321
     133>>> d[1] = 'test'
     134Traceback (most recent call last):
     135  File "<stdin>", line 1, in <module>
     136  File "/var/lib/python-support/python2.5/django/utils/datastructures.py", line 359, in complain
     137    raise AttributeError, self.warning
     138AttributeError: Object is immutable!
    128139"""
  • tests/regressiontests/forms/error_messages.py

    diff --git a/tests/regressiontests/forms/error_messages.py b/tests/regressiontests/forms/error_messages.py
    index 9f972f5..c7b301b 100644
    a b  
    11# -*- coding: utf-8 -*-
    22tests = r"""
    33>>> from django.newforms import *
     4>>> from django.core.files.uploadedfile import SimpleUploadedFile
    45
    56# CharField ###################################################################
    67
    ValidationError: [u'REQUIRED']  
    214215Traceback (most recent call last):
    215216...
    216217ValidationError: [u'INVALID']
    217 >>> f.clean({})
     218>>> f.clean(SimpleUploadedFile('name', None))
    218219Traceback (most recent call last):
    219220...
    220 ValidationError: [u'MISSING']
    221 >>> f.clean({'filename': 'name', 'content':''})
     221ValidationError: [u'EMPTY FILE']
     222>>> f.clean(SimpleUploadedFile('name', ''))
    222223Traceback (most recent call last):
    223224...
    224225ValidationError: [u'EMPTY FILE']
  • tests/regressiontests/forms/fields.py

    diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py
    index f3b6a96..7bfadb7 100644
    a b  
    22tests = r"""
    33>>> from django.newforms import *
    44>>> from django.newforms.widgets import RadioFieldRenderer
     5>>> from django.core.files.uploadedfile import SimpleUploadedFile
    56>>> import datetime
    67>>> import time
    78>>> import re
    ValidationError: [u'This field is required.']  
    773774>>> f.clean({})
    774775Traceback (most recent call last):
    775776...
    776 ValidationError: [u'No file was submitted.']
     777ValidationError: [u'No file was submitted. Check the encoding type on the form.']
    777778
    778779>>> f.clean({}, '')
    779780Traceback (most recent call last):
    780781...
    781 ValidationError: [u'No file was submitted.']
     782ValidationError: [u'No file was submitted. Check the encoding type on the form.']
    782783
    783784>>> f.clean({}, 'files/test3.pdf')
    784785'files/test3.pdf'
    Traceback (most recent call last):  
    788789...
    789790ValidationError: [u'No file was submitted. Check the encoding type on the form.']
    790791
    791 >>> f.clean({'filename': 'name', 'content': None})
     792>>> f.clean(SimpleUploadedFile('name', None))
    792793Traceback (most recent call last):
    793794...
    794795ValidationError: [u'The submitted file is empty.']
    795796
    796 >>> f.clean({'filename': 'name', 'content': ''})
     797>>> f.clean(SimpleUploadedFile('name', ''))
    797798Traceback (most recent call last):
    798799...
    799800ValidationError: [u'The submitted file is empty.']
    800801
    801 >>> type(f.clean({'filename': 'name', 'content': 'Some File Content'}))
     802>>> type(f.clean(SimpleUploadedFile('name', 'Some File Content')))
    802803<class 'django.newforms.fields.UploadedFile'>
    803804
    804 >>> type(f.clean({'filename': 'name', 'content': 'Some File Content'}, 'files/test4.pdf'))
     805>>> type(f.clean(SimpleUploadedFile('name', 'Some File Content'), 'files/test4.pdf'))
    805806<class 'django.newforms.fields.UploadedFile'>
    806807
    807808# URLField ##################################################################
  • tests/regressiontests/forms/forms.py

    diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py
    index 7fc206d..041fa40 100644
    a b  
    11# -*- coding: utf-8 -*-
    22tests = r"""
    33>>> from django.newforms import *
     4>>> from django.core.files.uploadedfile import SimpleUploadedFile
    45>>> import datetime
    56>>> import time
    67>>> import re
    not request.POST.  
    14651466>>> print f
    14661467<tr><th>File1:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="file" name="file1" /></td></tr>
    14671468
    1468 >>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':''}}, auto_id=False)
     1469>>> f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', '')}, auto_id=False)
    14691470>>> print f
    14701471<tr><th>File1:</th><td><ul class="errorlist"><li>The submitted file is empty.</li></ul><input type="file" name="file1" /></td></tr>
    14711472
    not request.POST.  
    14731474>>> print f
    14741475<tr><th>File1:</th><td><ul class="errorlist"><li>No file was submitted. Check the encoding type on the form.</li></ul><input type="file" name="file1" /></td></tr>
    14751476
    1476 >>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':'some content'}}, auto_id=False)
     1477>>> f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', 'some content')}, auto_id=False)
    14771478>>> print f
    14781479<tr><th>File1:</th><td><input type="file" name="file1" /></td></tr>
    14791480>>> f.is_valid()
  • tests/regressiontests/forms/tests.py

    diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py
    index bb0e30b..136e691 100644
    a b from localflavor.za import tests as localflavor_za_tests  
    2626from regressions import tests as regression_tests
    2727from util import tests as util_tests
    2828from widgets import tests as widgets_tests
     29from warnings import filterwarnings
     30filterwarnings("ignore")
    2931
    3032__test__ = {
    3133    'extra_tests': extra_tests,
  • tests/regressiontests/test_client_regress/models.py

    diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py
    index a204ec3..7f89b3a 100644
    a b from django.test import Client, TestCase  
    66from django.core.urlresolvers import reverse
    77from django.core.exceptions import SuspiciousOperation
    88import os
     9import sha
    910
    1011class AssertContainsTests(TestCase):
    1112    def test_contains(self):
    class FileUploadTests(TestCase):  
    250251        response = self.client.post('/test_client_regress/file_upload/', post_data)
    251252        self.assertEqual(response.status_code, 200)
    252253
     254    def test_large_upload(self):
     255        import tempfile
     256        dir = tempfile.gettempdir()
     257
     258        (fd, name1) = tempfile.mkstemp(suffix='.file1', dir=dir)
     259        file1 = os.fdopen(fd, 'w+b')
     260        file1.write('a' * (2 ** 21))
     261        file1.seek(0)
     262
     263        (fd, name2) = tempfile.mkstemp(suffix='.file2', dir=dir)
     264        file2 = os.fdopen(fd, 'w+b')
     265        file2.write('a' * (10 * 2 ** 20))
     266        file2.seek(0)
     267
     268        # This file contains chinese symbols for a name.
     269        name3 = os.path.join(dir, u'test_&#20013;&#25991;_Orl\u00e9ans.jpg')
     270        file3 = open(name3, 'w+b')
     271        file3.write('b' * (2 ** 10))
     272        file3.seek(0)
     273
     274        post_data = {
     275            'name': 'Ringo',
     276            'file_field1': file1,
     277            'file_field2': file2,
     278            'file_unicode': file3,
     279            }
     280
     281        for key in post_data.keys():
     282            try:
     283                post_data[key + '_hash'] = sha.new(post_data[key].read()).hexdigest()
     284                post_data[key].seek(0)
     285            except AttributeError:
     286                post_data[key + '_hash'] = sha.new(post_data[key]).hexdigest()
     287
     288        response = self.client.post('/test_client_regress/file_upload_verify/', post_data)
     289
     290        for name in (name1, name2, name3):
     291            try:
     292                os.unlink(name)
     293            except:
     294                pass
     295
     296        self.assertEqual(response.status_code, 200)
     297
     298
    253299class LoginTests(TestCase):
    254300    fixtures = ['testdata']
    255301
  • tests/regressiontests/test_client_regress/urls.py

    diff --git a/tests/regressiontests/test_client_regress/urls.py b/tests/regressiontests/test_client_regress/urls.py
    index dc26d12..a667766 100644
    a b import views  
    44urlpatterns = patterns('',
    55    (r'^no_template_view/$', views.no_template_view),
    66    (r'^file_upload/$', views.file_upload_view),
     7    (r'^file_upload_verify/$', views.file_upload_view_verify),
    78    (r'^staff_only/$', views.staff_only_view),
    89    (r'^get_view/$', views.get_view),
    910    url(r'^arg_view/(?P<name>.+)/$', views.view_with_argument, name='arg_view'),
  • tests/regressiontests/test_client_regress/views.py

    diff --git a/tests/regressiontests/test_client_regress/views.py b/tests/regressiontests/test_client_regress/views.py
    index 9632c17..1ad3ea1 100644
    a b import os  
    33from django.contrib.auth.decorators import login_required
    44from django.http import HttpResponse, HttpResponseRedirect, HttpResponseServerError
    55from django.core.exceptions import SuspiciousOperation
     6import sha
    67
    78def no_template_view(request):
    89    "A simple view that expects a GET request, and returns a rendered template"
    def file_upload_view(request):  
    1314    Check that a file upload can be updated into the POST dictionary without
    1415    going pear-shaped.
    1516    """
     17    from django.core.files.uploadedfile import UploadedFile
    1618    form_data = request.POST.copy()
    1719    form_data.update(request.FILES)
    18     if isinstance(form_data['file_field'], dict) and isinstance(form_data['name'], unicode):
     20    if isinstance(form_data.get('file_field'), UploadedFile) and isinstance(form_data['name'], unicode):
    1921        # If a file is posted, the dummy client should only post the file name,
    2022        # not the full path.
    21         if os.path.dirname(form_data['file_field']['filename']) != '':
     23        if os.path.dirname(form_data['file_field'].file_name) != '':
    2224            return HttpResponseServerError()           
    2325        return HttpResponse('')
    2426    else:
    def staff_only_view(request):  
    3133    else:
    3234        raise SuspiciousOperation()
    3335   
     36def file_upload_view_verify(request):
     37    """
     38    Use the sha digest hash to verify the uploaded contents.
     39    """
     40    from django.core.files.uploadedfile import UploadedFile
     41    form_data = request.POST.copy()
     42    form_data.update(request.FILES)
     43
     44    # Check to see if unicode names worked out.
     45    if not request.FILES['file_unicode'].file_name.endswith(u'test_\u4e2d\u6587_Orl\xe9ans.jpg'):
     46        return HttpResponseServerError()
     47
     48    for key, value in form_data.items():
     49        if key.endswith('_hash'):
     50            continue
     51        if key + '_hash' not in form_data:
     52            continue
     53        submitted_hash = form_data[key + '_hash']
     54        if isinstance(value, UploadedFile):
     55            new_hash = sha.new(value.read()).hexdigest()
     56        else:
     57            new_hash = sha.new(value).hexdigest()
     58        if new_hash != submitted_hash:
     59            return HttpResponseServerError()
     60
     61    return HttpResponse('')
     62
    3463def get_view(request):
    3564    "A simple login protected view"
    3665    return HttpResponse("Hello world")
    def view_with_argument(request, name):  
    5180def login_protected_redirect_view(request):
    5281    "A view that redirects all requests to the GET view"
    5382    return HttpResponseRedirect('/test_client_regress/get_view/')
    54 login_protected_redirect_view = login_required(login_protected_redirect_view)
    55  No newline at end of file
     83login_protected_redirect_view = login_required(login_protected_redirect_view)
Back to Top