diff -r c8038a5c808d django/conf/global_settings.py
--- a/django/conf/global_settings.py	Sat Aug 14 15:50:46 2010 +0000
+++ b/django/conf/global_settings.py	Sun Aug 15 18:18:26 2010 +0200
@@ -258,6 +258,15 @@
 # Default file storage mechanism that holds media.
 DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
 
+# High-level transfer backends
+FILE_TRANSFER_BACKENDS = (
+    'django.core.files.transfers.DefaultFileBackend',
+)
+
+# The default transfer backend's configuration mapping transfer_id to
+# backend name
+DEFAULT_FILE_TRANSFER_BACKENDS = {}
+
 # Absolute path to the directory that holds media.
 # Example: "/home/media/media.lawrence.com/"
 MEDIA_ROOT = ''
diff -r c8038a5c808d django/contrib/admin/options.py
--- a/django/contrib/admin/options.py	Sat Aug 14 15:50:46 2010 +0000
+++ b/django/contrib/admin/options.py	Sun Aug 15 18:18:26 2010 +0200
@@ -9,6 +9,7 @@
 from django.contrib import messages
 from django.views.decorators.csrf import csrf_protect
 from django.core.exceptions import PermissionDenied, ValidationError
+from django.core.files.transfers import prepare_upload
 from django.db import models, transaction
 from django.db.models.fields import BLANK_CHOICE_DASH
 from django.http import Http404, HttpResponse, HttpResponseRedirect
@@ -602,17 +603,45 @@
         """
         formset.save()
 
-    def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
+    def has_file_field(self, form, formsets):
+        for field in form.fields.values():
+            if isinstance(field, forms.FileField):
+                return True
+        for formset in formsets:
+            for form in formset.forms:
+                for field in form.fields.values():
+                    if isinstance(field, forms.FileField):
+                        return True
+        return False
+
+    def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None, has_file_field=True):
         opts = self.model._meta
         app_label = opts.app_label
         ordered_objects = opts.get_ordered_objects()
+
+        if has_file_field:
+            if not form_url:
+                form_url = request.get_full_path()
+            transfer_id = '%s.%s' % (self.__class__.__module__,
+                                     self.__class__.__name__)
+            if add:
+                transfer_id += '.add'
+            if change:
+                transfer_id += '.change'
+            form_url, upload_data = prepare_upload(transfer_id, request, url=form_url)
+            context.update({
+                'file_upload_data': mark_safe(''.join('<input type="hidden" name="%s" value="%s" />'
+                                                      % (escape(name), escape(value))
+                                                      for name, value in upload_data.items())),
+            })
+
         context.update({
             'add': add,
             'change': change,
             'has_add_permission': self.has_add_permission(request),
             'has_change_permission': self.has_change_permission(request, obj),
             'has_delete_permission': self.has_delete_permission(request, obj),
-            'has_file_field': True, # FIXME - this should check if form or formsets have a FileField,
+            'has_file_field': has_file_field,
             'has_absolute_url': hasattr(self.model, 'get_absolute_url'),
             'ordered_objects': ordered_objects,
             'form_url': mark_safe(form_url),
@@ -822,6 +851,8 @@
                                   queryset=inline.queryset(request))
                 formsets.append(formset)
 
+        has_file_field = self.has_file_field(form, formsets)
+
         adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)),
             self.prepopulated_fields, self.get_readonly_fields(request),
             model_admin=self)
@@ -848,7 +879,7 @@
             'app_label': opts.app_label,
         }
         context.update(extra_context or {})
-        return self.render_change_form(request, context, form_url=form_url, add=True)
+        return self.render_change_form(request, context, form_url=form_url, add=True, has_file_field=has_file_field)
 
     @csrf_protect_m
     @transaction.commit_on_success
@@ -913,6 +944,8 @@
                                   queryset=inline.queryset(request))
                 formsets.append(formset)
 
+        has_file_field = self.has_file_field(form, formsets)
+
         adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj),
             self.prepopulated_fields, self.get_readonly_fields(request, obj),
             model_admin=self)
@@ -940,7 +973,7 @@
             'app_label': opts.app_label,
         }
         context.update(extra_context or {})
-        return self.render_change_form(request, context, change=True, obj=obj)
+        return self.render_change_form(request, context, change=True, obj=obj, has_file_field=has_file_field)
 
     @csrf_protect_m
     def changelist_view(self, request, extra_context=None):
diff -r c8038a5c808d django/contrib/admin/templates/admin/change_form.html
--- a/django/contrib/admin/templates/admin/change_form.html	Sat Aug 14 15:50:46 2010 +0000
+++ b/django/contrib/admin/templates/admin/change_form.html	Sun Aug 15 18:18:26 2010 +0200
@@ -41,6 +41,8 @@
     {{ adminform.form.non_field_errors }}
 {% endif %}
 
+{{ file_upload_data }}
+
 {% for fieldset in adminform %}
   {% include "admin/includes/fieldset.html" %}
 {% endfor %}
diff -r c8038a5c808d django/contrib/admin/widgets.py
--- a/django/contrib/admin/widgets.py	Sat Aug 14 15:50:46 2010 +0000
+++ b/django/contrib/admin/widgets.py	Sun Aug 15 18:18:26 2010 +0200
@@ -94,9 +94,9 @@
 
     def render(self, name, value, attrs=None):
         output = []
-        if value and hasattr(value, "url"):
+        if value and hasattr(value, 'download_url'):
             output.append('%s <a target="_blank" href="%s">%s</a> <br />%s ' % \
-                (_('Currently:'), value.url, value, _('Change:')))
+                (_('Currently:'), value.download_url(), value, _('Change:')))
         output.append(super(AdminFileWidget, self).render(name, value, attrs))
         return mark_safe(u''.join(output))
 
diff -r c8038a5c808d django/core/files/transfers.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/files/transfers.py	Sun Aug 15 18:18:26 2010 +0200
@@ -0,0 +1,149 @@
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.core.files.storage import default_storage
+from django.http import HttpResponse
+from django.utils.encoding import smart_str
+from django.utils.importlib import import_module
+import mimetypes
+import urlparse
+
+class BaseFileBackend(object):
+    def __init__(self, transfer_id):
+        self.transfer_id = transfer_id
+
+    def prepare_upload(self, request, url):
+        return None
+
+    def serve(self, request, file, save_as=False, content_type=None):
+        return None
+
+    def download_url(self, file):
+        return None
+
+    def get_storage_backend(self):
+        return None
+
+class DefaultFileBackend(BaseFileBackend):
+    def _get_backend_path(self):
+        if self.transfer_id in settings.DEFAULT_FILE_TRANSFER_BACKENDS:
+            return settings.DEFAULT_FILE_TRANSFER_BACKENDS[self.transfer_id]
+        parts = self.transfer_id.split('.')[:-1]
+        while parts:
+            name = '.'.join(parts) + '.*'
+            if name in settings.DEFAULT_FILE_TRANSFER_BACKENDS:
+                return settings.DEFAULT_FILE_TRANSFER_BACKENDS[name]
+            del parts[-1]
+        return None
+
+    def _get_backend(self):
+        path = self._get_backend_path()
+        if path:
+            return load_backend(path, self.transfer_id)
+        return None
+
+    def prepare_upload(self, *args, **kwargs):
+        backend = self._get_backend()
+        if backend:
+            return backend.prepare_upload(*args, **kwargs)
+        return None
+
+    def serve(self, *args, **kwargs):
+        backend = self._get_backend()
+        if backend:
+            return backend.serve(*args, **kwargs)
+        return None
+
+    def download_url(self, *args, **kwargs):
+        backend = self._get_backend()
+        if backend:
+            return backend.download_url(*args, **kwargs)
+        return None
+
+    def get_storage_backend(self, *args, **kwargs):
+        backend = self._get_backend()
+        if backend:
+            return backend.get_storage_backend(*args, **kwargs)
+        return None
+
+# Public API
+def prepare_upload(transfer_id, request, url=None):
+    if not url:
+        url = request.get_full_path()
+    for backend in _get_backends(transfer_id):
+        result = backend.prepare_upload(request, url)
+        if result is not None:
+            return result
+
+    # By default we simply return the URL unmodified
+    return url, {}
+
+def serve_file(transfer_id, request, file, save_as=False, content_type=None):
+    filename = file.name.rsplit('/')[-1]
+    if save_as is True:
+        save_as = filename
+    if not content_type:
+        content_type = mimetypes.guess_type(filename)[0]
+    for backend in _get_backends(transfer_id):
+        result = backend.serve(request, file, save_as=save_as, content_type=content_type)
+        if result is not None:
+            return result
+
+    return _default_serve_file(file, save_as, content_type)
+
+def download_url(transfer_id, file):
+    for backend in _get_backends(transfer_id):
+        result = backend.download_url(file)
+        if result is not None:
+            return result
+
+    # By default we use MEDIA_URL
+    return urlparse.urljoin(settings.MEDIA_URL, file.name).replace('\\', '/')
+
+def get_storage_backend(transfer_id):
+    for backend in _get_backends(transfer_id):
+        result = backend.get_storage_backend()
+        if result is not None:
+            return result
+
+    # By default there is no public download URL
+    return default_storage
+
+# Internal utilities
+class ChunkedFile(object):
+    def __init__(self, file):
+        self.file = file
+
+    def __iter__(self):
+        return self.file.chunks()
+
+def _default_serve_file(file, save_as, content_type):    
+    """
+    Serves the file in chunks for efficiency reasons.
+    
+    The transfer still goes through Django itself, so it's much worse than
+    using the web server, but at least it works with all configurations.
+    """
+    response = HttpResponse(ChunkedFile(file), content_type=content_type)
+    if save_as:
+        response['Content-Disposition'] = smart_str(u'attachment; filename=%s' % save_as)
+    if file.size is not None:
+        response['Content-Length'] = file.size
+    return response
+
+def _get_backends(*args, **kwargs):
+    return (load_backend(name, *args, **kwargs)
+            for name in settings.FILE_TRANSFER_BACKENDS)
+
+def load_backend(path, *args, **kwargs):
+    module_name, attr_name = path.rsplit('.', 1)
+    try:
+        mod = import_module(module_name)
+    except ImportError, e:
+        raise ImproperlyConfigured('Error importing file backend module %s: "%s"' % (module_name, e))
+    except ValueError, e:
+        raise ImproperlyConfigured('Error importing file backend module. Is FILE_TRANSFER_BACKENDS a correctly defined list or tuple?')
+    try:
+        backend = getattr(mod, attr_name)
+    except AttributeError:
+        raise ImproperlyConfigured('Module "%s" does not define a "%s" file backend' % (module_name, attr_name))
+    return backend(*args, **kwargs)
diff -r c8038a5c808d django/core/files/uploadhandler.py
--- a/django/core/files/uploadhandler.py	Sat Aug 14 15:50:46 2010 +0000
+++ b/django/core/files/uploadhandler.py	Sun Aug 15 18:18:26 2010 +0200
@@ -84,7 +84,7 @@
         """
         pass
 
-    def new_file(self, field_name, file_name, content_type, content_length, charset=None):
+    def new_file(self, field_name, file_name, content_type, content_length,  charset=None, content_type_extra=None):
         """
         Signal that a new file has been started.
 
@@ -96,6 +96,9 @@
         self.content_type = content_type
         self.content_length = content_length
         self.charset = charset
+        if content_type_extra is None:
+            content_type_extra = {}
+        self.content_type_extra = content_type_extra
 
     def receive_data_chunk(self, raw_data, start):
         """
diff -r c8038a5c808d django/db/models/fields/files.py
--- a/django/db/models/fields/files.py	Sat Aug 14 15:50:46 2010 +0000
+++ b/django/db/models/fields/files.py	Sun Aug 15 18:18:26 2010 +0200
@@ -6,8 +6,8 @@
 from django.conf import settings
 from django.db.models.fields import Field
 from django.core.files.base import File, ContentFile
-from django.core.files.storage import default_storage
 from django.core.files.images import ImageFile, get_image_dimensions
+from django.core.files.transfers import serve_file, download_url, get_storage_backend
 from django.core.files.uploadedfile import UploadedFile
 from django.utils.functional import curry
 from django.db.models import signals
@@ -139,6 +139,14 @@
         # be restored later, by FileDescriptor below.
         return {'name': self.name, 'closed': False, '_committed': True, '_file': None}
 
+    def serve(self, request, save_as=False, content_type=None):
+        self._require_file()
+        return serve_file(self.field.transfer_id, request, self, save_as=save_as, content_type=content_type)
+
+    def download_url(self):
+        self._require_file()
+        return download_url(self.field.transfer_id, self)
+
 class FileDescriptor(object):
     """
     The descriptor for the file attribute on the model instance. Returns a
@@ -219,12 +227,13 @@
 
     description = ugettext_lazy("File path")
 
-    def __init__(self, verbose_name=None, name=None, upload_to='', storage=None, **kwargs):
+    def __init__(self, verbose_name=None, name=None, upload_to='', storage=None, transfer_id=None, **kwargs):
         for arg in ('primary_key', 'unique'):
             if arg in kwargs:
                 raise TypeError("'%s' is not a valid argument for %s." % (arg, self.__class__))
 
-        self.storage = storage or default_storage
+        self.transfer_id = transfer_id
+        self.storage = storage
         self.upload_to = upload_to
         if callable(upload_to):
             self.generate_filename = upload_to
@@ -257,6 +266,12 @@
 
     def contribute_to_class(self, cls, name):
         super(FileField, self).contribute_to_class(cls, name)
+        if self.transfer_id is None:
+            self.transfer_id = '%s.%s.%s' % (cls.__module__,
+                                             cls.__name__,
+                                             self.name)
+        if self.storage is None:
+            self.storage = get_storage_backend(self.transfer_id)
         setattr(cls, self.name, self.descriptor_class(self))
         signals.post_delete.connect(self.delete_file, sender=cls)
 
diff -r c8038a5c808d django/forms/forms.py
--- a/django/forms/forms.py	Sat Aug 14 15:50:46 2010 +0000
+++ b/django/forms/forms.py	Sun Aug 15 18:18:26 2010 +0200
@@ -3,6 +3,7 @@
 """
 
 from django.core.exceptions import ValidationError
+from django.core.files.transfers import prepare_upload
 from django.utils.copycompat import deepcopy
 from django.utils.datastructures import SortedDict
 from django.utils.html import conditional_escape
@@ -10,10 +11,10 @@
 from django.utils.safestring import mark_safe
 
 from fields import Field, FileField
-from widgets import Media, media_property, TextInput, Textarea
+from widgets import Media, media_property, TextInput, Textarea, HiddenInput
 from util import flatatt, ErrorDict, ErrorList
 
-__all__ = ('BaseForm', 'Form')
+__all__ = ('BaseForm', 'Form', 'RequestForm')
 
 NON_FIELD_ERRORS = '__all__'
 
@@ -377,6 +378,53 @@
         """
         return [field for field in self if not field.is_hidden]
 
+class BaseRequestFormTraits(object):
+    def __init__(self, request, url=None, prepare_upload=True, transfer_id=None, **kwargs):
+        if request.method == 'POST':
+            kwargs.setdefault('data', request.POST)
+            kwargs.setdefault('files', request.FILES)
+
+        self.request = request
+
+        # _url is the original url
+        self._url = url or request.get_full_path()
+        if not transfer_id:
+            self.transfer_id = '%s.%s' % (self.__class__.__module__,
+                                          self.__class__.__name__)
+        self._file_upload_data = {}
+        super(BaseRequestFormTraits, self).__init__(**kwargs)
+        if prepare_upload:
+            self.prepare_upload()
+
+    def prepare_upload(self):
+        if self.files:
+            return
+
+        self._remove_upload_data()
+
+        # Only prepare upload if a FileField exists
+        if not self._has_file_field():
+            return
+
+        result = prepare_upload(self.transfer_id, self.request, url=self._url)
+        self.url, self._file_upload_data = result
+
+        self._add_upload_data()
+
+    def _has_file_field(self):
+        for field in self.fields.values():
+            if isinstance(field, FileField):
+                return True
+        return False
+
+    def _remove_upload_data(self):
+        for name in self._file_upload_data:
+            del self.fields[name]
+
+    def _add_upload_data(self):
+        for name, value in self._file_upload_data.items():
+            self.fields[name] = Field(initial=value, required=False, widget=HiddenInput)
+
 class Form(BaseForm):
     "A collection of Fields, plus their associated data."
     # This is a separate class from BaseForm in order to abstract the way
@@ -386,6 +434,9 @@
     # BaseForm itself has no way of designating self.fields.
     __metaclass__ = DeclarativeFieldsMetaclass
 
+class RequestForm(BaseRequestFormTraits, BaseForm):
+    __metaclass__ = DeclarativeFieldsMetaclass
+
 class BoundField(StrAndUnicode):
     "A Field plus data"
     def __init__(self, form, field, name):
diff -r c8038a5c808d django/forms/formsets.py
--- a/django/forms/formsets.py	Sat Aug 14 15:50:46 2010 +0000
+++ b/django/forms/formsets.py	Sun Aug 15 18:18:26 2010 +0200
@@ -1,13 +1,13 @@
-from forms import Form
+from forms import Form, BaseRequestFormTraits, RequestForm
 from django.core.exceptions import ValidationError
 from django.utils.encoding import StrAndUnicode
 from django.utils.safestring import mark_safe
 from django.utils.translation import ugettext as _
-from fields import IntegerField, BooleanField
+from fields import Field, IntegerField, BooleanField, FileField
 from widgets import Media, HiddenInput
 from util import ErrorList
 
-__all__ = ('BaseFormSet', 'all_valid')
+__all__ = ('BaseFormSet', 'BaseRequestFormSet', 'all_valid')
 
 # special field names
 TOTAL_FORM_COUNT = 'TOTAL_FORMS'
@@ -326,6 +326,47 @@
         forms = u' '.join([form.as_table() for form in self.forms])
         return mark_safe(u'\n'.join([unicode(self.management_form), forms]))
 
+class BaseRequestFormSetTraits(BaseRequestFormTraits):
+    def _construct_form(self, *args, **kwargs):
+        if isinstance(self.form, BaseRequestFormTraits):
+            kwargs.update({
+                'request': self.request,
+                'url': self._url,
+                'prepare_upload': False,
+            })
+        super(BaseRequestFormSetTraits, self)._construct_form(*args, **kwargs)
+
+    def _get_empty_form(self, *args, **kwargs):
+        if isinstance(self.form, BaseRequestFormTraits):
+            kwargs.update({
+                'request': self.request,
+                'url': self._url,
+                'prepare_upload': False,
+            })
+        super(BaseRequestFormSetTraits, self)._get_empty_form(*args, **kwargs)
+    empty_form = property(_get_empty_form)
+
+    def _has_file_field(self):
+        for form in self.forms:
+            for field in form.fields.values():
+                if isinstance(field, FileField):
+                    return True
+        return False
+
+    def _remove_upload_data(self):
+        if hasattr(self, '_upload_data_form'):
+            self.forms.remove(self._upload_data_form)
+
+    def _add_upload_data(self):
+        form = RequestForm(self.request)
+        for name, value in self._file_upload_data.items():
+            form.fields[name] = Field(initial=value, required=False, widget=HiddenInput)
+        self._upload_data_form = form
+        self.forms.append(form)    
+
+class BaseRequestFormSet(BaseRequestFormSetTraits, BaseFormSet):
+    pass
+
 def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False,
                     can_delete=False, max_num=None):
     """Return a FormSet for the given form class."""
diff -r c8038a5c808d django/forms/models.py
--- a/django/forms/models.py	Sat Aug 14 15:50:46 2010 +0000
+++ b/django/forms/models.py	Sun Aug 15 18:18:26 2010 +0200
@@ -12,16 +12,17 @@
 from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
 from django.core.validators import EMPTY_VALUES
 from util import ErrorList
-from forms import BaseForm, get_declared_fields
+from forms import BaseForm, BaseRequestFormTraits, get_declared_fields
 from fields import Field, ChoiceField
 from widgets import SelectMultiple, HiddenInput, MultipleHiddenInput
 from widgets import media_property
-from formsets import BaseFormSet, formset_factory, DELETION_FIELD_NAME
+from formsets import BaseFormSet, formset_factory, DELETION_FIELD_NAME, BaseRequestFormSetTraits
 
 __all__ = (
     'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
     'save_instance', 'form_for_fields', 'ModelChoiceField',
-    'ModelMultipleChoiceField',
+    'ModelMultipleChoiceField', 'RequestModelForm', 'BaseRequestModelFormSet',
+    'BaseInlineRequestFormSet',
 )
 
 def construct_instance(form, instance, fields=None, exclude=None):
@@ -201,7 +202,7 @@
         formfield_callback = attrs.pop('formfield_callback',
                 lambda f, **kwargs: f.formfield(**kwargs))
         try:
-            parents = [b for b in bases if issubclass(b, ModelForm)]
+            parents = [b for b in bases if issubclass(b, (BaseModelForm))]
         except NameError:
             # We are defining ModelForm itself.
             parents = None
@@ -375,6 +376,9 @@
 class ModelForm(BaseModelForm):
     __metaclass__ = ModelFormMetaclass
 
+class RequestModelForm(BaseRequestFormTraits, BaseModelForm):
+    __metaclass__ = ModelFormMetaclass
+
 def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
                        formfield_callback=lambda f: f.formfield()):
     # Create the inner Meta class. FIXME: ideally, we should be able to
@@ -658,6 +662,9 @@
             form.fields[self._pk_field.name] = ModelChoiceField(qs, initial=pk_value, required=False, widget=HiddenInput)
         super(BaseModelFormSet, self).add_fields(form, index)
 
+class BaseRequestModelFormSet(BaseRequestFormSetTraits, BaseModelFormSet):
+    pass
+
 def modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f.formfield(),
                          formset=BaseModelFormSet,
                          extra=1, can_delete=False, can_order=False,
@@ -768,6 +775,8 @@
         unique_check = [field for field in unique_check if field != self.fk.name]
         return super(BaseInlineFormSet, self).get_unique_error_message(unique_check)
 
+class BaseInlineRequestFormSet(BaseRequestFormSetTraits, BaseInlineFormSet):
+    pass
 
 def _get_foreign_key(parent_model, model, fk_name=None, can_fail=False):
     """
diff -r c8038a5c808d django/http/multipartparser.py
--- a/django/http/multipartparser.py	Sat Aug 14 15:50:46 2010 +0000
+++ b/django/http/multipartparser.py	Sun Aug 15 18:18:26 2010 +0200
@@ -169,8 +169,9 @@
                     file_name = self.IE_sanitize(unescape_entities(file_name))
 
                     content_type = meta_data.get('content-type', ('',))[0].strip()
+                    content_type_extra = meta_data.get('content-type', (0,{}))[1]
                     try:
-                        charset = meta_data.get('content-type', (0,{}))[1].get('charset', None)
+                        charset = content_type_extra.get('charset', None)
                     except:
                         charset = None
 
@@ -185,7 +186,7 @@
                             try:
                                 handler.new_file(field_name, file_name,
                                                  content_type, content_length,
-                                                 charset)
+                                                 charset, content_type_extra)
                             except StopFutureHandlers:
                                 break
 
diff -r c8038a5c808d tests/regressiontests/file_uploads/backends.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/regressiontests/file_uploads/backends.py	Sun Aug 15 18:18:26 2010 +0200
@@ -0,0 +1,19 @@
+from django.conf import settings
+from django.core.files.transfers import BaseFileBackend
+from django.http import HttpResponseRedirect
+import models
+
+class TestFileBackend(BaseFileBackend):
+    def prepare_upload(self, request, url, *args, **kwargs):
+        return '/redirect' + url, {'x-extra': 'abcd', 'x-data': 'efg'}
+
+    def serve(self, request, file, *args, **kwargs):
+        if not file.name.endswith('pass'):
+            return HttpResponseRedirect('/download/')
+
+    def download_url(self, file):
+        if not file.name.endswith('pass'):
+            return '/public/'
+
+    def get_storage_backend(self, *args, **kwargs):
+        return models.temp_storage
diff -r c8038a5c808d tests/regressiontests/file_uploads/forms.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/regressiontests/file_uploads/forms.py	Sun Aug 15 18:18:26 2010 +0200
@@ -0,0 +1,6 @@
+from django import forms
+from models import FileModel
+
+class FileForm(forms.RequestModelForm):
+    class Meta:
+        model = FileModel
diff -r c8038a5c808d tests/regressiontests/file_uploads/models.py
--- a/tests/regressiontests/file_uploads/models.py	Sat Aug 14 15:50:46 2010 +0000
+++ b/tests/regressiontests/file_uploads/models.py	Sun Aug 15 18:18:26 2010 +0200
@@ -1,10 +1,14 @@
 import tempfile
 import os
+from django.conf import settings
 from django.db import models
 from django.core.files.storage import FileSystemStorage
 
 temp_storage = FileSystemStorage(tempfile.mkdtemp())
 UPLOAD_TO = os.path.join(temp_storage.location, 'test_upload')
+settings.DEFAULT_FILE_TRANSFER_BACKENDS.update({
+    'regressiontests.file_uploads.*': 'regressiontests.file_uploads.backends.TestFileBackend',
+})
 
 class FileModel(models.Model):
-    testfile = models.FileField(storage=temp_storage, upload_to='test_upload')
+    testfile = models.FileField(upload_to='test_upload')
diff -r c8038a5c808d tests/regressiontests/file_uploads/tests.py
--- a/tests/regressiontests/file_uploads/tests.py	Sat Aug 14 15:50:46 2010 +0000
+++ b/tests/regressiontests/file_uploads/tests.py	Sun Aug 15 18:18:26 2010 +0200
@@ -5,6 +5,7 @@
 import unittest
 from StringIO import StringIO
 
+from django.conf import settings
 from django.core.files import temp as tempfile
 from django.core.files.uploadedfile import SimpleUploadedFile
 from django.test import TestCase, client
@@ -17,6 +18,57 @@
 
 UNICODE_FILENAME = u'test-0123456789_中文_Orléans.jpg'
 
+class FileBackendTests(TestCase):
+    def setUp(self):
+        if not os.path.isdir(temp_storage.location):
+            os.makedirs(temp_storage.location)
+        if os.path.isdir(UPLOAD_TO):
+            shutil.rmtree(UPLOAD_TO)
+
+    def tearDown(self):
+        shutil.rmtree(temp_storage.location)
+
+    def test_prepare_upload(self):
+        response = self.client.get('/file_uploads/prepare_upload/')
+        self.assertEqual(response.status_code, 200)
+        data = simplejson.loads(response.content)
+        self.assertEqual(data, [
+            '/redirect/file_uploads/prepare_upload/', [
+                '<input type="file" name="testfile" id="id_testfile" />',
+                '<input type="hidden" name="x-data" value="efg" id="id_x-data" />',
+                '<input type="hidden" name="x-extra" value="abcd" id="id_x-extra" />',
+            ]
+        ])
+
+    def test_serve_file(self):
+        obj = FileModel()
+        obj.testfile.save('foo.txt', SimpleUploadedFile('foo.txt', 'x'))
+        obj.save()
+        response = self.client.get('/file_uploads/serve/')
+        self.assertEqual(response.status_code, 302)
+        self.assertEqual(response['Location'], 'http://testserver/download/')
+
+    def test_serve_file_fallback(self):
+        obj = FileModel()
+        obj.testfile.save('foo.pass', SimpleUploadedFile('foo.pass', 'x'))
+        obj.save()
+        response = self.client.get('/file_uploads/serve/')
+        self.assertEqual(response.status_code, 200)
+
+    def test_download_url(self):
+        obj = FileModel()
+        obj.testfile.save('foo2.txt', SimpleUploadedFile('foo2.txt', 'x'))
+        obj.save()
+        url = obj.testfile.download_url()
+        self.assertEqual(url, '/public/')
+
+    def test_download_url_fallback(self):
+        obj = FileModel()
+        obj.testfile.save('foo2.pass', SimpleUploadedFile('foo2.pass', 'x'))
+        obj.save()
+        url = obj.testfile.download_url()
+        self.assertEqual(url, settings.MEDIA_URL + obj.testfile.name)
+
 class FileUploadTests(TestCase):
     def test_simple_upload(self):
         post_data = {
@@ -122,9 +174,9 @@
         response = self.client.request(**r)
 
         # The filenames should have been sanitized by the time it got to the view.
-        recieved = simplejson.loads(response.content)
+        received = simplejson.loads(response.content)
         for i, name in enumerate(scary_file_names):
-            got = recieved["file%s" % i]
+            got = received["file%s" % i]
             self.assertEqual(got, "hax0rd.txt")
 
     def test_filename_overflow(self):
diff -r c8038a5c808d tests/regressiontests/file_uploads/urls.py
--- a/tests/regressiontests/file_uploads/urls.py	Sat Aug 14 15:50:46 2010 +0000
+++ b/tests/regressiontests/file_uploads/urls.py	Sun Aug 15 18:18:26 2010 +0200
@@ -10,4 +10,6 @@
     (r'^quota/broken/$',    views.file_upload_quota_broken),
     (r'^getlist_count/$',   views.file_upload_getlist_count),
     (r'^upload_errors/$',   views.file_upload_errors),
+    (r'^prepare_upload/$',  views.file_prepare_upload),
+    (r'^serve/$', views.file_serve),
 )
diff -r c8038a5c808d tests/regressiontests/file_uploads/views.py
--- a/tests/regressiontests/file_uploads/views.py	Sat Aug 14 15:50:46 2010 +0000
+++ b/tests/regressiontests/file_uploads/views.py	Sun Aug 15 18:18:26 2010 +0200
@@ -2,6 +2,7 @@
 from django.core.files.uploadedfile import UploadedFile
 from django.http import HttpResponse, HttpResponseServerError
 from django.utils import simplejson
+from forms import FileForm
 from models import FileModel, UPLOAD_TO
 from uploadhandler import QuotaUploadHandler, ErroringUploadHandler
 from django.utils.hashcompat import sha_constructor
@@ -112,3 +113,10 @@
 def file_upload_errors(request):
     request.upload_handlers.insert(0, ErroringUploadHandler())
     return file_upload_echo(request)
+
+def file_prepare_upload(request):
+    form = FileForm(request)
+    return HttpResponse(simplejson.dumps([form.url, list(map(unicode, form))]))
+
+def file_serve(request):
+    return FileModel.objects.all()[0].testfile.serve(request)
