Code

Ticket #2070: 2070-r7695.patch

File 2070-r7695.patch, 100.6 KB (added by jacob, 6 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)