Index: django/test/client.py
===================================================================
--- django/test/client.py	(revision 2453)
+++ django/test/client.py	(working copy)
@@ -19,6 +19,24 @@
 BOUNDARY = 'BoUnDaRyStRiNg'
 MULTIPART_CONTENT = 'multipart/form-data; boundary=%s' % BOUNDARY
 
+class FakePayload(object):
+    """
+    A wrapper around StringIO that restricts what can be read,
+    since data from the network can't be seeked and cannot
+    be read outside of its content length (or else we hang).
+    """
+    def __init__(self, content):
+        self.__content = StringIO(content)
+        self.__len = len(content)
+
+    def read(self, num_bytes=None):
+        if num_bytes is None:
+            num_bytes = self.__len or 1
+        assert self.__len >= num_bytes, "Cannot read more than the available bytes from the HTTP incoming data."
+        content = self.__content.read(num_bytes)
+        self.__len -= num_bytes
+        return content
+
 class ClientHandler(BaseHandler):
     """
     A HTTP Handler that can be used for testing purposes.
@@ -236,7 +254,7 @@
             'CONTENT_TYPE':   content_type,
             'PATH_INFO':      urllib.unquote(path),
             'REQUEST_METHOD': 'POST',
-            'wsgi.input':     StringIO(post_data),
+            'wsgi.input':     FakePayload(post_data),
         }
         r.update(extra)
 
@@ -280,4 +298,4 @@
         """
         session = __import__(settings.SESSION_ENGINE, {}, {}, ['']).SessionStore()
         session.delete(session_key=self.cookies[settings.SESSION_COOKIE_NAME].value)
-        self.cookies = SimpleCookie()
+        self.cookies = SimpleCookie()
\ No newline at end of file
Index: django/http/__init__.py
===================================================================
--- django/http/__init__.py	(revision 2453)
+++ django/http/__init__.py	(working copy)
@@ -9,9 +9,9 @@
 except ImportError:
     from cgi import parse_qsl
 
-from django.utils.datastructures import MultiValueDict, FileDict
+from django.utils.datastructures import MultiValueDict, ImmutableList
 from django.utils.encoding import smart_str, iri_to_uri, force_unicode
-
+from django.http.multipartparser import MultiPartParser
 from utils import *
 
 RESERVED_CHARS="!*'();:@&=+$,/?%#[]"
@@ -25,6 +25,7 @@
 
     # The encoding used in GET/POST dicts. None means use default setting.
     _encoding = None
+    _upload_handlers = ()
 
     def __init__(self):
         self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
@@ -102,40 +103,40 @@
 
     encoding = property(_get_encoding, _set_encoding)
 
-def parse_file_upload(header_dict, post_data):
-    """Returns a tuple of (POST QueryDict, FILES MultiValueDict)."""
-    import email, email.Message
-    from cgi import parse_header
-    raw_message = '\r\n'.join(['%s:%s' % pair for pair in header_dict.items()])
-    raw_message += '\r\n\r\n' + post_data
-    msg = email.message_from_string(raw_message)
-    POST = QueryDict('', mutable=True)
-    FILES = MultiValueDict()
-    for submessage in msg.get_payload():
-        if submessage and isinstance(submessage, email.Message.Message):
-            name_dict = parse_header(submessage['Content-Disposition'])[1]
-            # name_dict is something like {'name': 'file', 'filename': 'test.txt'} for file uploads
-            # or {'name': 'blah'} for POST fields
-            # We assume all uploaded files have a 'filename' set.
-            if 'filename' in name_dict:
-                assert type([]) != type(submessage.get_payload()), "Nested MIME messages are not supported"
-                if not name_dict['filename'].strip():
-                    continue
-                # IE submits the full path, so trim everything but the basename.
-                # (We can't use os.path.basename because that uses the server's
-                # directory separator, which may not be the same as the
-                # client's one.)
-                filename = name_dict['filename'][name_dict['filename'].rfind("\\")+1:]
-                FILES.appendlist(name_dict['name'], FileDict({
-                    'filename': filename,
-                    'content-type': 'Content-Type' in submessage and submessage['Content-Type'] or None,
-                    'content': submessage.get_payload(),
-                }))
-            else:
-                POST.appendlist(name_dict['name'], submessage.get_payload())
-    return POST, FILES
+    def _initialize_handlers(self):
+        from django.conf import settings
+        from django.core.files.uploadhandler import load_handler
+        handlers = []
+        # We go through each handler in the settings variable
+        # and instantiate the handler by calling HandlerClass(request).
+        for handler in settings.FILE_UPLOAD_HANDLERS:
+            handlers.append(load_handler(handler, self))
+        self._upload_handlers = handlers
 
+    def _set_upload_handlers(self, upload_handlers):
+        """
+        Set the upload handler to the new handler given in the parameter.
+        """
+        if hasattr(self, '_files'):
+            raise AttributeError("You cannot set the upload handlers after the upload has been processed.")
+        self._upload_handlers = upload_handlers
 
+    def _get_upload_handlers(self):
+        if not self._upload_handlers:
+            # If thre are no upload handlers defined, initialize them from settings.
+            self._initialize_handlers()
+        return self._upload_handlers
+
+    upload_handlers = property(_get_upload_handlers, _set_upload_handlers)
+
+    def parse_file_upload(self, META, post_data):
+        """Returns a tuple of (POST QueryDict, FILES MultiValueDict)."""
+        self.upload_handlers = ImmutableList(self.upload_handlers,
+                                             warning="You cannot alter the upload handlers after the upload has been processed.")
+        parser = MultiPartParser(META, post_data, self.upload_handlers,
+                                 self.encoding)
+        return parser.parse()
+
 class QueryDict(MultiValueDict):
     """
     A specialized MultiValueDict that takes a query string when initialized.
Index: django/oldforms/__init__.py
===================================================================
--- django/oldforms/__init__.py	(revision 2453)
+++ django/oldforms/__init__.py	(working copy)
@@ -680,18 +680,27 @@
         self.field_name, self.is_required = field_name, is_required
         self.validator_list = [self.isNonEmptyFile] + validator_list
 
-    def isNonEmptyFile(self, field_data, all_data):
+    def isNonEmptyFile(self, new_data, all_data):
+        if hasattr(new_data, 'upload_errors'):
+            upload_errors = new_data.upload_errors()
+            if upload_errors:
+                raise validators.CriticalValidationError, upload_errors
         try:
-            content = field_data['content']
-        except TypeError:
-            raise validators.CriticalValidationError, ugettext("No file was submitted. Check the encoding type on the form.")
-        if not content:
+            file_size = new_data.file_size
+        except AttributeError:
+            file_size = len(new_data['content'])
+        if not file_size:
             raise validators.CriticalValidationError, ugettext("The submitted file is empty.")
 
     def render(self, data):
         return mark_safe(u'<input type="file" id="%s" class="v%s" name="%s" />' % \
             (self.get_id(), self.__class__.__name__, self.field_name))
 
+    def prepare(self, new_data):
+        if hasattr(new_data, 'upload_errors'):
+            upload_errors = new_data.upload_errors()
+            new_data[self.field_name] = { '_file_upload_error': upload_errors }
+
     def html2python(data):
         if data is None:
             raise EmptyValue
Index: django/db/models/base.py
===================================================================
--- django/db/models/base.py	(revision 2453)
+++ django/db/models/base.py	(working copy)
@@ -19,6 +19,7 @@
 from django.utils.datastructures import SortedDict
 from django.utils.functional import curry
 from django.utils.encoding import smart_str, force_unicode, smart_unicode
+from django.core.files.move import file_move_safe
 from django.conf import settings
 
 try:
@@ -447,12 +448,29 @@
     def _get_FIELD_size(self, field):
         return os.path.getsize(self._get_FIELD_filename(field))
 
-    def _save_FIELD_file(self, field, filename, raw_contents, save=True):
+    def _save_FIELD_file(self, field, filename, raw_field, save=True):
         directory = field.get_directory_name()
         try: # Create the date-based directory if it doesn't exist.
             os.makedirs(os.path.join(settings.MEDIA_ROOT, directory))
         except OSError: # Directory probably already exists.
             pass
+
+        # Put the deprecation warning first since there are multiple
+        # locations where we use the new and old interface.
+        if isinstance(raw_field, dict):
+            import warnings
+            from django.core.files.uploadedfile import SimpleUploadedFile
+            raw_field = SimpleUploadedFile.from_dict(raw_field)
+            warnings.warn("The dictionary usage for files is deprecated. Use the new object interface instead.", DeprecationWarning)
+        elif isinstance(raw_field, basestring):
+            import warnings
+            from django.core.files.uploadedfile import SimpleUploadedFile
+            raw_field = SimpleUploadedFile(filename, raw_field)
+            warnings.warn("The string interface for save_FIELD_file is deprecated.", DeprecationWarning)
+
+        if filename is None:
+            filename = raw_field.file_name
+
         filename = field.get_filename(filename)
 
         # If the filename already exists, keep adding an underscore to the name of
@@ -469,9 +487,20 @@
         setattr(self, field.attname, filename)
 
         full_filename = self._get_FIELD_filename(field)
-        fp = open(full_filename, 'wb')
-        fp.write(raw_contents)
-        fp.close()
+        if hasattr(raw_field, 'temporary_file_path'):
+            # This file has a file path that we can move.
+            raw_field.close()
+            file_move_safe(raw_field.temporary_file_path(), full_filename)
+        else:
+            from django.core.files import locks
+            fp = open(full_filename, 'wb')
+            # exclusive lock
+            locks.lock(fp, locks.LOCK_EX)
+            # This is a normal uploadedfile that we can stream.
+            for chunk in raw_field.chunk(65535):
+                fp.write(chunk)
+            locks.unlock(fp)
+            fp.close()
 
         # Save the width and/or height, if applicable.
         if isinstance(field, ImageField) and (field.width_field or field.height_field):
Index: django/db/models/fields/__init__.py
===================================================================
--- django/db/models/fields/__init__.py	(revision 2453)
+++ django/db/models/fields/__init__.py	(working copy)
@@ -806,7 +806,7 @@
         setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self))
         setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self))
         setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self))
-        setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save))
+        setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_field, save=True: instance._save_FIELD_file(self, filename, raw_field, save))
         dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls)
 
     def delete_file(self, instance):
@@ -829,10 +829,16 @@
         if new_data.get(upload_field_name, False):
             func = getattr(new_object, 'save_%s_file' % self.name)
             if rel:
-                func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"], save)
+                file = new_data[upload_field_name][0]
             else:
-                func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"], save)
+                file = new_data[upload_field_name]
 
+            try:
+                file_name = file.file_name
+            except AttributeError:
+                file_name = file['filename']
+            func(file_name, file, save)
+
     def get_directory_name(self):
         return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to))))
 
@@ -844,7 +850,7 @@
     def save_form_data(self, instance, data):
         from django.newforms.fields import UploadedFile
         if data and isinstance(data, UploadedFile):
-            getattr(instance, "save_%s_file" % self.name)(data.filename, data.content, save=False)
+            getattr(instance, "save_%s_file" % self.name)(data.filename, data.data, save=False)
 
     def formfield(self, **kwargs):
         defaults = {'form_class': forms.FileField}
Index: django/conf/global_settings.py
===================================================================
--- django/conf/global_settings.py	(revision 2453)
+++ django/conf/global_settings.py	(working copy)
@@ -228,6 +228,22 @@
 # Example: "http://media.lawrence.com"
 MEDIA_URL = ''
 
+# A tuple that enumerates the upload handlers
+# in order.
+FILE_UPLOAD_HANDLERS = (
+    'django.core.files.uploadhandler.MemoryFileUploadHandler',
+    'django.core.files.uploadhandler.TemporaryFileUploadHandler',
+)
+
+# Number of bytes the length of the request can be before it is 
+# streamed to the file system instead of parsed entirely in memory.
+FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440
+
+# Directory to upload streamed files temporarily.
+# A value of `None` means that it will use the default temporary
+# directory for the server's operating system.
+FILE_UPLOAD_TEMP_DIR = None
+
 # Default formatting for date objects. See all available format strings here:
 # http://www.djangoproject.com/documentation/templates/#now
 DATE_FORMAT = 'N j, Y'
Index: django/core/handlers/wsgi.py
===================================================================
--- django/core/handlers/wsgi.py	(revision 2453)
+++ django/core/handlers/wsgi.py	(working copy)
@@ -112,9 +112,8 @@
         # Populates self._post and self._files
         if self.method == 'POST':
             if self.environ.get('CONTENT_TYPE', '').startswith('multipart'):
-                header_dict = dict([(k, v) for k, v in self.environ.items() if k.startswith('HTTP_')])
-                header_dict['Content-Type'] = self.environ.get('CONTENT_TYPE', '')
-                self._post, self._files = http.parse_file_upload(header_dict, self.raw_post_data)
+                self._raw_post_data = ''
+                self._post, self._files = self.parse_file_upload(self.META, self.environ['wsgi.input'])
             else:
                 self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
         else:
Index: django/core/handlers/modpython.py
===================================================================
--- django/core/handlers/modpython.py	(revision 2453)
+++ django/core/handlers/modpython.py	(working copy)
@@ -53,7 +53,8 @@
     def _load_post_and_files(self):
         "Populates self._post and self._files"
         if 'content-type' in self._req.headers_in and self._req.headers_in['content-type'].startswith('multipart'):
-            self._post, self._files = http.parse_file_upload(self._req.headers_in, self.raw_post_data)
+            self._raw_post_data = ''
+            self._post, self._files = self.parse_file_upload(self.META, self._req)
         else:
             self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
 
Index: django/newforms/fields.py
===================================================================
--- django/newforms/fields.py	(revision 2453)
+++ django/newforms/fields.py	(working copy)
@@ -4,6 +4,7 @@
 
 import copy
 import datetime
+import warnings
 import os
 import re
 import time
@@ -416,9 +417,9 @@
 
 class UploadedFile(StrAndUnicode):
     "A wrapper for files uploaded in a FileField"
-    def __init__(self, filename, content):
+    def __init__(self, filename, data):
         self.filename = filename
-        self.content = content
+        self.data = data
 
     def __unicode__(self):
         """
@@ -444,16 +445,28 @@
             return None
         elif not data and initial:
             return initial
+
+        if isinstance(data, dict):
+            # We warn once, then support both ways below.
+            warnings.warn("The dictionary usage for files is deprecated. Use the new object interface instead.", DeprecationWarning)
+
         try:
-            f = UploadedFile(data['filename'], data['content'])
-        except TypeError:
+            file_name = data.file_name
+            file_size = data.file_size
+        except AttributeError:
+            try:
+                file_name = data.get('filename')
+                file_size = bool(data['content'])
+            except (AttributeError, KeyError):
+                raise ValidationError(self.error_messages['invalid'])
+
+        if not file_name:
             raise ValidationError(self.error_messages['invalid'])
-        except KeyError:
-            raise ValidationError(self.error_messages['missing'])
-        if not f.content:
+        if not file_size:
             raise ValidationError(self.error_messages['empty'])
-        return f
 
+        return UploadedFile(file_name, data)
+
 class ImageField(FileField):
     default_error_messages = {
         'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
@@ -470,15 +483,36 @@
         elif not data and initial:
             return initial
         from PIL import Image
-        from cStringIO import StringIO
+
+        # We need to get the file, it either has a path
+        # or we have to read it all into memory...
+        if hasattr(data, 'temporary_file_path'):
+            file = data.temporary_file_path()
+        else:
+            try:
+                from cStringIO import StringIO
+            except ImportError:
+                from StringIO import StringIO
+            if hasattr(data, 'read'):
+                file = StringIO(data.read())
+            else:
+                file = StringIO(data['content'])
+
         try:
             # load() is the only method that can spot a truncated JPEG,
             #  but it cannot be called sanely after verify()
-            trial_image = Image.open(StringIO(f.content))
+            trial_image = Image.open(file)
             trial_image.load()
+
+            # Since we're about to use the file again, we have to 
+            # reset the cursor of the file object if it has a cursor
+            # to reset.
+            if hasattr(file, 'reset'):
+                file.reset()
+
             # verify() is the only method that can spot a corrupt PNG,
             #  but it must be called immediately after the constructor
-            trial_image = Image.open(StringIO(f.content))
+            trial_image = Image.open(file)
             trial_image.verify()
         except Exception: # Python Imaging Library doesn't recognize it as an image
             raise ValidationError(self.error_messages['invalid_image'])
Index: django/utils/datastructures.py
===================================================================
--- django/utils/datastructures.py	(revision 2453)
+++ django/utils/datastructures.py	(working copy)
@@ -332,14 +332,34 @@
             except TypeError: # Special-case if current isn't a dict.
                 current = {bits[-1]: v}
 
-class FileDict(dict):
+class ImmutableList(tuple):
     """
-    A dictionary used to hold uploaded file contents. The only special feature
-    here is that repr() of this object won't dump the entire contents of the
-    file to the output. A handy safeguard for a large file upload.
+    A tuple-like object that raises useful
+    errors when it is asked to mutate.
+    Example::
+        a = ImmutableList(range(5), warning=AttributeError("You cannot mutate this."))
+        a[3] = '4'
+        (Raises the AttributeError)
     """
-    def __repr__(self):
-        if 'content' in self:
-            d = dict(self, content='<omitted>')
-            return dict.__repr__(d)
-        return dict.__repr__(self)
+
+    def __new__(cls, *args, **kwargs):
+        if 'warning' in kwargs:
+            warning = kwargs['warning']
+            del kwargs['warning']
+        else:
+            warning = 'ImmutableList object is immutable.'
+        self = tuple.__new__(cls, *args, **kwargs)
+        self.warning = warning
+        return self
+
+    def complain(self, *wargs, **kwargs):
+        if isinstance(self.warning, Exception):
+            raise self.warning
+        else:
+            raise AttributeError, self.warning
+
+    # All list mutation functions become complain.
+    __delitem__ = __delslice__ = __iadd__ = __imul__ = complain
+    __setitem__ = __setslice__ = complain
+    append = extend = insert = pop = remove = complain
+    sort = reverse = complain
Index: django/utils/text.py
===================================================================
--- django/utils/text.py	(revision 2453)
+++ django/utils/text.py	(working copy)
@@ -3,6 +3,7 @@
 from django.utils.encoding import force_unicode
 from django.utils.functional import allow_lazy
 from django.utils.translation import ugettext_lazy
+from htmlentitydefs import name2codepoint
 
 # Capitalizes the first letter of a string.
 capfirst = lambda x: x and force_unicode(x)[0].upper() + force_unicode(x)[1:]
@@ -222,3 +223,26 @@
             yield bit
 smart_split = allow_lazy(smart_split, unicode)
 
+def _replace_entity(match):
+     text = match.group(1)
+     if text[0] == u'#':
+         text = text[1:]
+         try:
+             if text[0] in u'xX':
+                 c = int(text[1:], 16)
+             else:
+                 c = int(text)
+             return unichr(c)
+         except ValueError:
+             return match.group(0)
+     else:
+         try:
+             return unichr(name2codepoint[text])
+         except (ValueError, KeyError):
+             return match.group(0)
+
+_entity_re = re.compile(r"&(#?[xX]?(?:[0-9a-fA-F]+|\w{1,8}));")
+
+def unescape_entities(text):
+     return _entity_re.sub(_replace_entity, text)
+unescape_entities = allow_lazy(unescape_entities, unicode)
Index: tests/modeltests/model_forms/models.py
===================================================================
--- tests/modeltests/model_forms/models.py	(revision 2453)
+++ tests/modeltests/model_forms/models.py	(working copy)
@@ -67,7 +67,13 @@
 
 class ImageFile(models.Model):
     description = models.CharField(max_length=20)
-    image = models.FileField(upload_to=tempfile.gettempdir())
+    try:
+        # If PIL is available, try testing PIL.
+        # Otherwise, it's equivalent to TextFile above.
+        import Image
+        image = models.ImageField(upload_to=tempfile.gettempdir())
+    except ImportError:
+        image = models.FileField(upload_to=tempfile.gettempdir())
 
     def __unicode__(self):
         return self.description
@@ -75,6 +81,9 @@
 __test__ = {'API_TESTS': """
 >>> from django import newforms as forms
 >>> from django.newforms.models import ModelForm
+>>> from django.core.files.uploadedfile import SimpleUploadedFile
+>>> from warnings import filterwarnings
+>>> filterwarnings("ignore")
 
 The bare bones, absolutely nothing custom, basic case.
 
@@ -792,6 +801,17 @@
 
 # Upload a file and ensure it all works as expected.
 
+>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test1.txt', 'hello world')})
+>>> f.is_valid()
+True
+>>> type(f.cleaned_data['file'])
+<class 'django.newforms.fields.UploadedFile'>
+>>> instance = f.save()
+>>> instance.file
+u'...test1.txt'
+
+>>> os.unlink(instance.get_file_filename())
+
 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test1.txt', 'content': 'hello world'}})
 >>> f.is_valid()
 True
@@ -814,18 +834,30 @@
 u'...test1.txt'
 
 # Delete the current file since this is not done by Django.
-
 >>> os.unlink(instance.get_file_filename())
 
 # Override the file by uploading a new one.
 
->>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test2.txt', 'content': 'hello world'}}, instance=instance)
+>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test2.txt', 'hello world')}, instance=instance)
 >>> f.is_valid()
 True
 >>> instance = f.save()
 >>> instance.file
 u'...test2.txt'
 
+# Delete the current file since this is not done by Django.
+>>> os.unlink(instance.get_file_filename())
+
+>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test2.txt', 'content': 'hello world'}})
+>>> f.is_valid()
+True
+>>> instance = f.save()
+>>> instance.file
+u'...test2.txt'
+
+# Delete the current file since this is not done by Django.
+>>> os.unlink(instance.get_file_filename())
+
 >>> instance.delete()
 
 # Test the non-required FileField
@@ -838,14 +870,28 @@
 >>> instance.file
 ''
 
->>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test3.txt', 'content': 'hello world'}}, instance=instance)
+>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')}, instance=instance)
 >>> f.is_valid()
 True
 >>> instance = f.save()
 >>> instance.file
 u'...test3.txt'
+
+# Delete the current file since this is not done by Django.
+>>> os.unlink(instance.get_file_filename())
 >>> instance.delete()
 
+>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test3.txt', 'content': 'hello world'}})
+>>> f.is_valid()
+True
+>>> instance = f.save()
+>>> instance.file
+u'...test3.txt'
+
+# Delete the current file since this is not done by Django.
+>>> os.unlink(instance.get_file_filename())
+>>> instance.delete()
+
 # ImageField ###################################################################
 
 # ImageField and FileField are nearly identical, but they differ slighty when
@@ -858,6 +904,18 @@
 
 >>> image_data = open(os.path.join(os.path.dirname(__file__), "test.png")).read()
 
+>>> f = ImageFileForm(data={'description': u'An image'}, files={'image': SimpleUploadedFile('test.png', image_data)})
+>>> f.is_valid()
+True
+>>> type(f.cleaned_data['image'])
+<class 'django.newforms.fields.UploadedFile'>
+>>> instance = f.save()
+>>> instance.image
+u'...test.png'
+
+# Delete the current file since this is not done by Django.
+>>> os.unlink(instance.get_image_filename())
+
 >>> f = ImageFileForm(data={'description': u'An image'}, files={'image': {'filename': 'test.png', 'content': image_data}})
 >>> f.is_valid()
 True
@@ -885,15 +943,28 @@
 
 # Override the file by uploading a new one.
 
->>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': {'filename': 'test2.png', 'content': image_data}}, instance=instance)
+>>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': SimpleUploadedFile('test2.png', image_data)}, instance=instance)
 >>> f.is_valid()
 True
 >>> instance = f.save()
 >>> instance.image
 u'...test2.png'
 
+# Delete the current file since this is not done by Django.
+>>> os.unlink(instance.get_image_filename())
 >>> instance.delete()
 
+>>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': {'filename': 'test2.png', 'content': image_data}})
+>>> f.is_valid()
+True
+>>> instance = f.save()
+>>> instance.image
+u'...test2.png'
+
+# Delete the current file since this is not done by Django.
+>>> os.unlink(instance.get_image_filename())
+>>> instance.delete()
+
 # Test the non-required ImageField
 
 >>> f = ImageFileForm(data={'description': u'Test'})
@@ -904,12 +975,23 @@
 >>> instance.image
 ''
 
->>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': {'filename': 'test3.png', 'content': image_data}}, instance=instance)
+>>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': SimpleUploadedFile('test3.png', image_data)}, instance=instance)
 >>> f.is_valid()
 True
 >>> instance = f.save()
 >>> instance.image
 u'...test3.png'
+
+# Delete the current file since this is not done by Django.
+>>> os.unlink(instance.get_image_filename())
 >>> instance.delete()
 
+>>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': {'filename': 'test3.png', 'content': image_data}})
+>>> f.is_valid()
+True
+>>> instance = f.save()
+>>> instance.image
+u'...test3.png'
+>>> instance.delete()
+
 """}
Index: tests/regressiontests/bug639/tests.py
===================================================================
--- tests/regressiontests/bug639/tests.py	(revision 2453)
+++ tests/regressiontests/bug639/tests.py	(working copy)
@@ -9,6 +9,7 @@
 from regressiontests.bug639.models import Photo
 from django.http import QueryDict
 from django.utils.datastructures import MultiValueDict
+from django.core.files.uploadedfile import SimpleUploadedFile
 
 class Bug639Test(unittest.TestCase):
         
@@ -21,12 +22,8 @@
         
         # Fake a request query dict with the file
         qd = QueryDict("title=Testing&image=", mutable=True)
-        qd["image_file"] = {
-            "filename" : "test.jpg",
-            "content-type" : "image/jpeg",
-            "content" : img
-        }
-        
+        qd["image_file"] = SimpleUploadedFile('test.jpg', img, 'image/jpeg')
+
         manip = Photo.AddManipulator()
         manip.do_html2python(qd)
         p = manip.save(qd)
@@ -39,4 +36,4 @@
         Make sure to delete the "uploaded" file to avoid clogging /tmp.
         """
         p = Photo.objects.get()
-        os.unlink(p.get_image_filename())
\ No newline at end of file
+        os.unlink(p.get_image_filename())
Index: tests/regressiontests/forms/error_messages.py
===================================================================
--- tests/regressiontests/forms/error_messages.py	(revision 2453)
+++ tests/regressiontests/forms/error_messages.py	(working copy)
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 tests = r"""
 >>> from django.newforms import *
+>>> from django.core.files.uploadedfile import SimpleUploadedFile
 
 # CharField ###################################################################
 
@@ -214,11 +215,11 @@
 Traceback (most recent call last):
 ...
 ValidationError: [u'INVALID']
->>> f.clean({})
+>>> f.clean(SimpleUploadedFile('name', None))
 Traceback (most recent call last):
 ...
-ValidationError: [u'MISSING']
->>> f.clean({'filename': 'name', 'content':''})
+ValidationError: [u'EMPTY FILE']
+>>> f.clean(SimpleUploadedFile('name', ''))
 Traceback (most recent call last):
 ...
 ValidationError: [u'EMPTY FILE']
Index: tests/regressiontests/forms/tests.py
===================================================================
--- tests/regressiontests/forms/tests.py	(revision 2453)
+++ tests/regressiontests/forms/tests.py	(working copy)
@@ -26,6 +26,8 @@
 from regressions import tests as regression_tests
 from util import tests as util_tests
 from widgets import tests as widgets_tests
+from warnings import filterwarnings
+filterwarnings("ignore")
 
 __test__ = {
     'extra_tests': extra_tests,
Index: tests/regressiontests/forms/fields.py
===================================================================
--- tests/regressiontests/forms/fields.py	(revision 2453)
+++ tests/regressiontests/forms/fields.py	(working copy)
@@ -2,6 +2,7 @@
 tests = r"""
 >>> from django.newforms import *
 >>> from django.newforms.widgets import RadioFieldRenderer
+>>> from django.core.files.uploadedfile import SimpleUploadedFile
 >>> import datetime
 >>> import time
 >>> import re
@@ -773,12 +774,12 @@
 >>> f.clean({})
 Traceback (most recent call last):
 ...
-ValidationError: [u'No file was submitted.']
+ValidationError: [u'No file was submitted. Check the encoding type on the form.']
 
 >>> f.clean({}, '')
 Traceback (most recent call last):
 ...
-ValidationError: [u'No file was submitted.']
+ValidationError: [u'No file was submitted. Check the encoding type on the form.']
 
 >>> f.clean({}, 'files/test3.pdf')
 'files/test3.pdf'
@@ -788,20 +789,20 @@
 ...
 ValidationError: [u'No file was submitted. Check the encoding type on the form.']
 
->>> f.clean({'filename': 'name', 'content': None})
+>>> f.clean(SimpleUploadedFile('name', None))
 Traceback (most recent call last):
 ...
 ValidationError: [u'The submitted file is empty.']
 
->>> f.clean({'filename': 'name', 'content': ''})
+>>> f.clean(SimpleUploadedFile('name', ''))
 Traceback (most recent call last):
 ...
 ValidationError: [u'The submitted file is empty.']
 
->>> type(f.clean({'filename': 'name', 'content': 'Some File Content'}))
+>>> type(f.clean(SimpleUploadedFile('name', 'Some File Content')))
 <class 'django.newforms.fields.UploadedFile'>
 
->>> type(f.clean({'filename': 'name', 'content': 'Some File Content'}, 'files/test4.pdf'))
+>>> type(f.clean(SimpleUploadedFile('name', 'Some File Content'), 'files/test4.pdf'))
 <class 'django.newforms.fields.UploadedFile'>
 
 # URLField ##################################################################
Index: tests/regressiontests/forms/forms.py
===================================================================
--- tests/regressiontests/forms/forms.py	(revision 2453)
+++ tests/regressiontests/forms/forms.py	(working copy)
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 tests = r"""
 >>> from django.newforms import *
+>>> from django.core.files.uploadedfile import SimpleUploadedFile
 >>> import datetime
 >>> import time
 >>> import re
@@ -1465,7 +1466,7 @@
 >>> print f
 <tr><th>File1:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="file" name="file1" /></td></tr>
 
->>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':''}}, auto_id=False)
+>>> f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', '')}, auto_id=False)
 >>> print f
 <tr><th>File1:</th><td><ul class="errorlist"><li>The submitted file is empty.</li></ul><input type="file" name="file1" /></td></tr>
 
@@ -1473,7 +1474,7 @@
 >>> print f
 <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>
 
->>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':'some content'}}, auto_id=False)
+>>> f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', 'some content')}, auto_id=False)
 >>> print f
 <tr><th>File1:</th><td><input type="file" name="file1" /></td></tr>
 >>> f.is_valid()
Index: tests/regressiontests/test_client_regress/views.py
===================================================================
--- tests/regressiontests/test_client_regress/views.py	(revision 2453)
+++ tests/regressiontests/test_client_regress/views.py	(working copy)
@@ -1,4 +1,5 @@
 import os
+import sha
 
 from django.contrib.auth.decorators import login_required
 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseServerError
@@ -13,9 +14,10 @@
     Check that a file upload can be updated into the POST dictionary without
     going pear-shaped.
     """
+    from django.core.files.uploadedfile import UploadedFile
     form_data = request.POST.copy()
     form_data.update(request.FILES)
-    if isinstance(form_data['file_field'], dict) and isinstance(form_data['name'], unicode):
+    if isinstance(form_data.get('file_field'), UploadedFile) and isinstance(form_data['name'], unicode):
         # If a file is posted, the dummy client should only post the file name,
         # not the full path.
         if os.path.dirname(form_data['file_field']['filename']) != '':
@@ -24,13 +26,40 @@
     else:
         return HttpResponseServerError()
 
+def file_upload_view_verify(request):
+    """
+    Use the sha digest hash to verify the uploaded contents.
+    """
+    from django.core.files.uploadedfile import UploadedFile
+    form_data = request.POST.copy()
+    form_data.update(request.FILES)
+
+    # Check to see if unicode names worked out.
+    if not request.FILES['file_unicode'].file_name.endswith(u'test_\u4e2d\u6587_Orl\xe9ans.jpg'):
+        return HttpResponseServerError()
+
+    for key, value in form_data.items():
+        if key.endswith('_hash'):
+            continue
+        if key + '_hash' not in form_data:
+            continue
+        submitted_hash = form_data[key + '_hash']
+        if isinstance(value, UploadedFile):
+            new_hash = sha.new(value.read()).hexdigest()
+        else:
+            new_hash = sha.new(value).hexdigest()
+        if new_hash != submitted_hash:
+            return HttpResponseServerError()
+
+    return HttpResponse('')
+
 def staff_only_view(request):
     "A view that can only be visited by staff. Non staff members get an exception"
     if request.user.is_staff:
         return HttpResponse('')
     else:
         raise SuspiciousOperation()
-    
+
 def get_view(request):
     "A simple login protected view"
     return HttpResponse("Hello world")
Index: tests/regressiontests/test_client_regress/models.py
===================================================================
--- tests/regressiontests/test_client_regress/models.py	(revision 2453)
+++ tests/regressiontests/test_client_regress/models.py	(working copy)
@@ -6,6 +6,7 @@
 from django.core.urlresolvers import reverse
 from django.core.exceptions import SuspiciousOperation
 import os
+import sha
 
 class AssertContainsTests(TestCase):
     def test_contains(self):
@@ -250,6 +251,50 @@
         response = self.client.post('/test_client_regress/file_upload/', post_data)
         self.assertEqual(response.status_code, 200)
 
+    def test_large_upload(self):
+        import tempfile
+        dir = tempfile.gettempdir()
+
+        (fd, name1) = tempfile.mkstemp(suffix='.file1', dir=dir)
+        file1 = os.fdopen(fd, 'w+b')
+        file1.write('a' * (2 ** 21))
+        file1.seek(0)
+
+        (fd, name2) = tempfile.mkstemp(suffix='.file2', dir=dir)
+        file2 = os.fdopen(fd, 'w+b')
+        file2.write('a' * (10 * 2 ** 20))
+        file2.seek(0)
+
+        # This file contains chinese symbols for a name.
+        name3 = os.path.join(dir, u'test_&#20013;&#25991;_Orl\u00e9ans.jpg')
+        file3 = open(name3, 'w+b')
+        file3.write('b' * (2 ** 10))
+        file3.seek(0)
+
+        post_data = {
+            'name': 'Ringo',
+            'file_field1': file1,
+            'file_field2': file2,
+            'file_unicode': file3,
+            }
+
+        for key in post_data.keys():
+            try:
+                post_data[key + '_hash'] = sha.new(post_data[key].read()).hexdigest()
+                post_data[key].seek(0)
+            except AttributeError:
+                post_data[key + '_hash'] = sha.new(post_data[key]).hexdigest()
+
+        response = self.client.post('/test_client_regress/file_upload_verify/', post_data)
+
+        for name in (name1, name2, name3):
+            try:
+                os.unlink(name)
+            except:
+                pass
+
+        self.assertEqual(response.status_code, 200)
+
 class LoginTests(TestCase):
     fixtures = ['testdata']
 
Index: tests/regressiontests/test_client_regress/urls.py
===================================================================
--- tests/regressiontests/test_client_regress/urls.py	(revision 2453)
+++ tests/regressiontests/test_client_regress/urls.py	(working copy)
@@ -4,6 +4,7 @@
 urlpatterns = patterns('',
     (r'^no_template_view/$', views.no_template_view),
     (r'^file_upload/$', views.file_upload_view),
+    (r'^file_upload_verify/$', views.file_upload_view_verify),
     (r'^staff_only/$', views.staff_only_view),
     (r'^get_view/$', views.get_view),
     url(r'^arg_view/(?P<name>.+)/$', views.view_with_argument, name='arg_view'),
Index: tests/regressiontests/datastructures/tests.py
===================================================================
--- tests/regressiontests/datastructures/tests.py	(revision 2453)
+++ tests/regressiontests/datastructures/tests.py	(working copy)
@@ -117,12 +117,23 @@
 >>> d['person']['2']['firstname']
 ['Adrian']
 
-### FileDict ################################################################
-
->>> d = FileDict({'content': 'once upon a time...'})
+### ImmutableList ################################################################
+>>> d = ImmutableList(range(10))
+>>> d.sort()
+Traceback (most recent call last):
+  File "<stdin>", line 1, in <module>
+  File "/var/lib/python-support/python2.5/django/utils/datastructures.py", line 359, in complain
+    raise AttributeError, self.warning
+AttributeError: ImmutableList object is immutable.
 >>> repr(d)
-"{'content': '<omitted>'}"
->>> d = FileDict({'other-key': 'once upon a time...'})
->>> repr(d)
-"{'other-key': 'once upon a time...'}"
+'(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)'
+>>> d = ImmutableList(range(10), warning="Object is immutable!")
+>>> d[1]
+1
+>>> d[1] = 'test'
+Traceback (most recent call last):
+  File "<stdin>", line 1, in <module>
+  File "/var/lib/python-support/python2.5/django/utils/datastructures.py", line 359, in complain
+    raise AttributeError, self.warning
+AttributeError: Object is immutable!
 """
Index: AUTHORS
===================================================================
--- AUTHORS	(revision 2453)
+++ AUTHORS	(working copy)
@@ -59,7 +59,7 @@
     Arthur <avandorp@gmail.com>
     av0000@mail.ru
     David Avsajanishvili <avsd05@gmail.com>
-    axiak@mit.edu
+    Mike Axiak <axiak@mit.edu>
     Niran Babalola <niran@niran.org>
     Morten Bagai <m@bagai.com>
     Mikaël Barbero <mikael.barbero nospam at nospam free.fr>
@@ -139,6 +139,7 @@
     Marc Fargas <telenieko@telenieko.com>
     Szilveszter Farkas <szilveszter.farkas@gmail.com>
     favo@exoweb.net
+    fdr <drfarina@gmail.com>
     Dmitri Fedortchenko <zeraien@gmail.com>
     Liang Feng <hutuworm@gmail.com>
     Bill Fenner <fenner@gmail.com>
Index: docs/request_response.txt
===================================================================
--- docs/request_response.txt	(revision 2453)
+++ docs/request_response.txt	(working copy)
@@ -80,20 +80,24 @@
     strings.
 
 ``FILES``
+   **New in Django development version**
     A dictionary-like object containing all uploaded files. Each key in
     ``FILES`` is the ``name`` from the ``<input type="file" name="" />``. Each
-    value in ``FILES`` is a standard Python dictionary with the following three
-    keys:
+    value in ``FILES`` is an ``UploadedFile`` object containing at least the
+    following attributes:
 
-        * ``filename`` -- The name of the uploaded file, as a Python string.
-        * ``content-type`` -- The content type of the uploaded file.
-        * ``content`` -- The raw content of the uploaded file.
+        * ``read(num_bytes=None)`` -- Read a number of bytes from the file.
+        * ``file_name`` -- The name of the uploaded file.
+        * ``file_size`` -- The size, in bytes, of the uploaded file.
+	* ``chunk()`` -- A generator that yields sequential chunks of data.
 
-    Note that ``FILES`` will only contain data if the request method was POST
-    and the ``<form>`` that posted to the request had
-    ``enctype="multipart/form-data"``. Otherwise, ``FILES`` will be a blank
-    dictionary-like object.
+    See `File Uploads`_ for more information. Note that ``FILES`` will only
+    contain data if the request method was POST and the ``<form>`` that posted
+    to the request had ``enctype="multipart/form-data"``. Otherwise, ``FILES``
+    will be a blank dictionary-like object.
 
+    .. _File Uploads: ../upload_handling/
+
 ``META``
     A standard Python dictionary containing all available HTTP headers.
     Available headers depend on the client and server, but here are some
Index: docs/settings.txt
===================================================================
--- docs/settings.txt	(revision 2453)
+++ docs/settings.txt	(working copy)
@@ -279,7 +279,7 @@
 
 The database backend to use. The build-in database backends are
 ``'postgresql_psycopg2'``, ``'postgresql'``, ``'mysql'``, ``'mysql_old'``,
-``'sqlite3'`` and ``'oracle'``.
+``'sqlite3'``, ``'oracle'``, and ``'oracle'``.
 
 In the Django development version, you can use a database backend that doesn't
 ship with Django by setting ``DATABASE_ENGINE`` to a fully-qualified path (i.e.
@@ -523,6 +523,36 @@
 The character encoding used to decode any files read from disk. This includes
 template files and initial SQL data files.
 
+FILE_UPLOAD_HANDLERS
+--------------------
+
+**New in Django development version**
+
+Default::
+
+    ("django.core.files.fileuploadhandler.MemoryFileUploadHandler",
+     "django.core.files.fileuploadhandler.TemporaryFileUploadHandler",)
+
+A tuple of handlers to use for uploading.
+
+FILE_UPLOAD_MAX_MEMORY_SIZE
+---------------------------
+
+**New in Django development version**
+
+Default: ``2621440``
+
+The maximum size (in bytes) that an upload will be before it gets streamed to the file system.
+
+FILE_UPLOAD_TEMP_DIR
+--------------------
+
+**New in Django development version**
+
+Default: ``None``
+
+The 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.
+
 FIXTURE_DIRS
 -------------
 
Index: docs/newforms.txt
===================================================================
--- docs/newforms.txt	(revision 2453)
+++ docs/newforms.txt	(working copy)
@@ -805,12 +805,12 @@
 need to bind the file data containing the mugshot image::
 
     # Bound form with an image field
+    >>> from django.core.files.uploadedfile import SimpleUploadedFile
     >>> data = {'subject': 'hello',
     ...         'message': 'Hi there',
     ...         'sender': 'foo@example.com',
     ...         'cc_myself': True}
-    >>> file_data = {'mugshot': {'filename':'face.jpg'
-    ...                          'content': <file data>}}
+    >>> file_data = {'mugshot': SimpleUploadedFile('face.jpg', <file data>)}
     >>> f = ContactFormWithMugshot(data, file_data)
 
 In practice, you will usually specify ``request.FILES`` as the source
