Index: django/contrib/admin/media/js/UploadProgress.js
===================================================================
--- django/contrib/admin/media/js/UploadProgress.js	(revision 0)
+++ django/contrib/admin/media/js/UploadProgress.js	(revision 0)
@@ -0,0 +1,205 @@
+
+var upload_progress_failures = 0;
+var TOTAL_UPLOAD_PROGRESS_FAILURES = 10;
+var upload_progress_num__IE = 0;
+var show_progress = false;
+/* below is the url from admin to the upload progress.
+   e.g. upload_progress/
+*/
+var progress_url  = "upload_progress/";
+
+var split_path = location.pathname.split('/');
+
+var new_array = new Array();
+
+var j=0;
+for (var i=0; i<split_path.length; i++) {
+    if (split_path[i] != '') {
+        new_array[j] = split_path[i];
+        j++;
+    }
+}
+
+admin_root = '/';
+if (new_array.length > 3) {
+    for (var i=0; i<new_array.length-3; i++) {
+        admin_root += new_array[i]+'/';
+    }
+}
+
+progress_url = admin_root + progress_url;
+
+function getxy(){
+    var x,y;
+    if (self.innerHeight) // all except Explorer
+        {
+        x = self.innerWidth;
+        y = self.innerHeight;
+        }
+    else if (document.documentElement && document.documentElement.clientHeight)
+        // Explorer 6 Strict Mode
+        {
+        x = document.documentElement.clientWidth;
+        y = document.documentElement.clientHeight;
+        }
+    else if (document.body) // other Explorers
+        {
+        x = document.body.clientWidth;
+        y = document.body.clientHeight;
+        }
+    return {'x':x,'y':y}
+    }
+
+
+function upload_ajax_problem() {
+    /* If there's a problem, will cancel after
+       TOTAL_UPLOAD_PROGRESS_FAILURES tries.   */
+
+    var progress_wrap2 = document.getElementById('progress_wrap');
+    upload_progress_failures++;
+    if (upload_progress_failures >= TOTAL_UPLOAD_PROGRESS_FAILURES){
+      window.clearTimeout(interval);
+    } 
+    progress_wrap2.style.display = 'none';
+    progress_wrap2.style.visibility = 'hidden';
+}
+
+var humanvalue = ['B','KB','MB','GB']
+function humanize(bytes) {
+  curbytes = bytes;
+  iterations = 0;
+  if (!curbytes) {
+     return '';
+  }
+  while (curbytes>1024) {
+    iterations++;
+    curbytes=curbytes/1024;
+  }
+  return curbytes.toFixed(1) + ' ' + humanvalue[iterations];
+}
+
+interval = null;
+function fetch(uuid) {
+  /* no ajax here */
+  if (!xmlhttp) {
+    upload_ajax_problem();
+    return;
+  }
+
+  req = xmlhttp;
+  req.open("GET", progress_url+"?" + upload_progress_num__IE, true);
+  upload_progress_num__IE++; // IE Hack
+  req.onreadystatechange = function () {
+    var progress_wrap2 = document.getElementById('progress_wrap');
+    var progress_bar2  = document.getElementById('progress_bar');
+    var bar_txt = document.getElementById('progress_text');
+
+    if (req.readyState == 4) {
+      try {
+        request_status = req.status;
+      } catch (e) {
+        /* Really bad. */
+        request_status = -1;
+      }
+      if (request_status == 200) {
+        var upload = new Function(" return "+req.responseText)();
+        if (upload) {
+
+          if (!upload.state) {
+             progress_wrap2.style.visibility = 'hidden';
+             progress_wrap2.style.display    = 'none';
+             return;
+          } else if (upload.state == 'done') {
+             window.clearTimeout(interval);
+             progress_wrap2.style.visibility = 'hidden';
+             progress_wrap2.style.display    = 'none';
+             return;   
+          } else {
+             if (show_progress) {
+                 progress_wrap2.style.visibility = 'visible';
+                 progress_wrap2.style.display    = 'block';
+             }
+             move_to_center(progress_wrap2);
+             bar_txt.innerHTML = ((upload.received / upload.size) * 100).toFixed(1)
+                     + '% - ' + humanize(upload.received) + ' of ' 
+                     + humanize(upload.size);
+             var bar_width__px = 400 * upload.received / upload.size;
+             progress_bar2.style.width = bar_width__px + 'px';
+          }
+        } else {
+          upload_ajax_problem();
+        }
+      } else {
+        upload_ajax_problem();
+      }
+    }
+  };
+  try {
+    req.setRequestHeader("X-Upload-Id", uuid);
+  } catch (e) {
+    /* couldn't set the header, the request is broken. */
+    req.abort();
+  }
+  req.send(null); 
+
+}
+
+function move_to_center(progress_wrap) {
+    pos = getxy();
+    posx = parseInt((pos.x/2)-(420/2), 10);
+    posy = parseInt((pos.y/2)-(50/2), 10);
+
+    progress_wrap.style.top  = posy + 'px';
+    progress_wrap.style.left = posx + 'px';   
+}
+
+function close_progress() {
+    var progress_wrap2 = document.getElementById('progress_wrap');
+    progress_wrap2.style.display = 'none';
+    progress_wrap2.style.visibility = 'hidden';
+    show_progress = false;
+    // don't want to follow a link.
+    return false;
+}
+
+
+function openprogress(e) {
+
+    show_progress = true;
+    upload_progress_failures = 0;
+    uuid = "";
+    for (i = 0; i < 32; i++) {
+        uuid += Math.floor(Math.random() * 16).toString(16);
+        }
+    frm = e.target||e.srcElement;
+
+    /*
+    var progress_wrap2 = document.getElementById('progress_wrap');
+    move_to_center(progress_wrap2);
+    progress_wrap2.style.display = 'block';
+    progress_wrap2.style.visibility = 'visible';
+    */
+
+    if (frm.action.indexOf('?') == -1) {
+       frm.action=frm.action+"?progress_id=" + uuid
+    } else {
+       frm.action=frm.action+"&progress_id=" + uuid;
+    }
+
+    interval = window.setInterval(
+        function () {
+            fetch(uuid);
+            },
+        1000
+        );
+}
+
+addEvent(window, 'load', function() {
+        frms = document.getElementsByTagName('form');
+        for (var i=0; i<frms.length; i++) {
+           if (frms[i].encoding.toLowerCase() == 'multipart/form-data') {
+              addEvent(frms[i], 'submit',  openprogress);
+              return;
+           }
+        }
+    });
Index: django/contrib/admin/urls.py
===================================================================
--- django/contrib/admin/urls.py	(revision 5343)
+++ django/contrib/admin/urls.py	(working copy)
@@ -10,6 +10,7 @@
     ('^$', 'django.contrib.admin.views.main.index'),
     ('^r/(\d+)/(.*)/$', 'django.views.defaults.shortcut'),
     ('^jsi18n/$', i18n_view, {'packages': 'django.conf'}),
+    ('^upload_progress/$', 'django.contrib.admin.views.main.upload_progress'),
     ('^logout/$', 'django.contrib.auth.views.logout'),
     ('^password_change/$', 'django.contrib.auth.views.password_change'),
     ('^password_change/done/$', 'django.contrib.auth.views.password_change_done'),
Index: django/contrib/admin/views/main.py
===================================================================
--- django/contrib/admin/views/main.py	(revision 5343)
+++ django/contrib/admin/views/main.py	(working copy)
@@ -9,7 +9,7 @@
 from django.shortcuts import get_object_or_404, render_to_response
 from django.db import models
 from django.db.models.query import handle_legacy_orderlist, QuerySet
-from django.http import Http404, HttpResponse, HttpResponseRedirect
+from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseServerError
 from django.utils.html import escape
 from django.utils.text import capfirst, get_text_list
 import operator
@@ -81,6 +81,8 @@
 def get_javascript_imports(opts, auto_populated_fields, field_sets):
 # Put in any necessary JavaScript imports.
     js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
+    if opts.has_field_type(models.FileField) and settings.FILE_UPLOAD_DIR:
+        js.append('js/UploadProgress.js')
     if auto_populated_fields:
         js.append('js/urlify.js')
     if opts.has_field_type(models.DateTimeField) or opts.has_field_type(models.TimeField) or opts.has_field_type(models.DateField):
@@ -777,3 +779,19 @@
                                'admin/%s/change_list.html' % app_label,
                                'admin/change_list.html'], context_instance=c)
 change_list = staff_member_required(never_cache(change_list))
+
+def upload_progress(request):
+    """
+    Given this request, returns a JSON
+    object that has information on a file upload progress.
+    If there is no file upload in progress, returns an
+    empty dictionary, '{}'.
+    """
+    from django.utils import simplejson
+
+    content = simplejson.dumps(request.file_progress)
+
+    if content.strip() == '{}':
+        return HttpResponseServerError('')
+    else:
+        return HttpResponse(content=content, mimetype='text/plain')
Index: django/contrib/admin/templates/admin/change_form.html
===================================================================
--- django/contrib/admin/templates/admin/change_form.html	(revision 5343)
+++ django/contrib/admin/templates/admin/change_form.html	(working copy)
@@ -65,6 +65,18 @@
    {% auto_populated_field_script auto_populated_fields change %}
    </script>
 {% endif %}
+
+{% if has_file_field %}
+<div id="progress_wrap" style="position: absolute; background: white; z-index: 9040; display: none; visibility: hidden; width: 420px; height: 50px padding: 10px; border: solid 1px #ddd;">
+   <a href="#" onclick="close_progress();return false" title="Close Progress Bar" 
+      style="color: #c00; font-size: 1.5em; font-weight: bold; float: right; padding: 0; position: relative; top: -2px; left: -2px;">X</a>
+   <h1>Upload progress</h1>
+
+   <div id="progress_bar" style="top: 0; left: 0; width: 0; z-index: 9049; height: 4px;" class="submit-row"></div>
+   <div id="progress_text" style="color: black;">0%</div>
 </div>
+{% endif %}
+
+</div>
 </form></div>
 {% endblock %}
Index: django/contrib/uploadprogress/models.py
===================================================================
--- django/contrib/uploadprogress/models.py	(revision 0)
+++ django/contrib/uploadprogress/models.py	(revision 0)
@@ -0,0 +1,77 @@
+"""
+Models file for a simple file upload progress application.
+This cause the file progress to be stored in the database.
+To activate:
+1) Add 'django.contrib.uploadprogress.middleware.FileProgressDB'
+   to your MIDDLEWARE_CLASSES setting.
+2) Add 'django.contrib.uploadprogress' to your INSTALLED_APPS.
+
+"""
+
+from django.db import models
+import datetime
+
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
+
+class FileProgress(models.Model):
+
+    remote_addr = models.CharField(maxlength=64)
+    progress_id = models.CharField(maxlength=32)
+    progress_text = models.TextField(editable = False)
+    last_ts     = models.DateTimeField()
+
+    class Meta:
+        verbose_name_plural = 'File Progresses'
+        unique_together = (('remote_addr',
+                           'progress_id',
+                           ),
+                           )
+
+    class Admin:
+        pass
+
+    def __str__(self):
+        return 'File Progress for "%s" and IP "%s".' % (self.progress_id, self.remote_addr)
+
+    def _get_dict(self):
+        """
+        Returns a dictionary object.
+        """
+        if hasattr(self, '_progress_dict'):
+            return self._progress_dict
+        
+        if not self.progress_text:
+            self._progress_dict = {}
+            return {}
+
+        try:
+            self._progress_dict = pickle.loads(self.progress_text)
+        except:
+            self._progress_dict = {}
+
+        return self._progress_dict
+
+    def _set_dict(self, dict):
+        """
+        Sets a dictionary object.
+        """
+        self._progress_dict = dict
+
+    progress = property(_get_dict, _set_dict)
+
+    def save(self, *args, **kwargs):
+        """
+        Pickles the dictionary representation
+        of the progress.
+        """
+        try:
+            self.progress_text = pickle.dumps(self.progress)
+        except:
+            pass
+
+        self.last_ts = datetime.datetime.now()
+
+        return super(FileProgress, self).save(*args, **kwargs)
Index: django/contrib/uploadprogress/middleware/uploadcache.py
===================================================================
--- django/contrib/uploadprogress/middleware/uploadcache.py	(revision 0)
+++ django/contrib/uploadprogress/middleware/uploadcache.py	(revision 0)
@@ -0,0 +1,59 @@
+"""
+
+Middleware to track file progress using the cache framework.
+To use, just add
+    'django.contrib.uploadprogress.middleware.FileProgressCached'
+to your ``MIDDLEWARE_SETTINGS``.
+
+If your cache framework does not work, this will not work either.
+
+"""
+from django.core.cache import cache
+from django.conf import settings
+from django.http.multipartparser import MultiPartParserError
+
+UPLOAD_CACHE_PREFIX = getattr(settings, 'UPLOAD_CACHE_PREFIX', 'UPLOAD_PROGRESS_')
+
+class FileProgressStore(object):
+
+    def _get_key(self, request):
+        """
+        Returns the cache prefix for any cache key.
+        Uses the IP Address as well as the randomly generated uuid.
+        """
+        
+        if hasattr(self, '_cache_key'):
+            return self._cache_key
+        
+        self._cache_key = '%s__%s__%s' % \
+                           (UPLOAD_CACHE_PREFIX,
+                            request.META['REMOTE_ADDR'],
+                            request.META['UPLOAD_PROGRESS_ID'],)
+
+        return self._cache_key
+
+    def __get__(self, request, HttpRequest):
+        return cache.get(self._get_key(request), {})
+
+    def __set__(self, request, new_val):
+        received_size = total_size = -1
+        try:
+            total_size = int(new_val['size'])
+        except:
+            pass
+
+        try:
+            received_size = int(new_val['received'])
+        except:
+            pass
+        
+        cache.set(self._get_key(request), new_val)
+
+    def __delete__(self, request):
+        cache.delete(self._get_key(request))
+
+class FileProgressCached(object):
+
+    def process_request(self, request):
+        # set the request.file_progress descriptor
+        request.__class__.file_progress = FileProgressStore()
Index: django/contrib/uploadprogress/middleware/uploaddb.py
===================================================================
--- django/contrib/uploadprogress/middleware/uploaddb.py	(revision 0)
+++ django/contrib/uploadprogress/middleware/uploaddb.py	(revision 0)
@@ -0,0 +1,47 @@
+"""
+
+Middleware to hold fileupload progress state in a database.
+To install, simply add
+    'django.contrib.uploadprogress.middleware.FileProgressDB'
+    
+to your ``MIDDLEWARE_SETTINGS`` and
+    'django.contrib.uploadprogress'
+to your ``INSTALLED_APPS``.
+
+Then run ``./manage.py syncdb`` as you normally would.
+
+"""
+from django.contrib.uploadprogress.models import FileProgress
+from django.http.multipartparser import MultiPartParserError
+from django.conf import settings
+
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
+
+class FileProgressDBStore(object):
+
+    def _get_db_row(self, request):
+        if not hasattr(self, '_db_row'):
+            self._db_row, created = FileProgress.objects.get_or_create(
+                                                 remote_addr = request.META['REMOTE_ADDR'],
+                                                 progress_id = request.META['UPLOAD_PROGRESS_ID'])
+
+        return self._db_row
+
+    def __get__(self, request, HttpRequest):
+        return self._get_db_row(request).progress
+
+    def __set__(self, request, new_val):
+        row = self._get_db_row(request)
+        row.progress = new_val
+        row.save()
+
+    def __delete__(self, request):
+        self._get_db_row(request).delete()
+
+class FileProgressDB(object):
+
+    def process_request(self, request):
+        request.__class__.file_progress = FileProgressDBStore()
Index: django/contrib/uploadprogress/middleware/__init__.py
===================================================================
--- django/contrib/uploadprogress/middleware/__init__.py	(revision 0)
+++ django/contrib/uploadprogress/middleware/__init__.py	(revision 0)
@@ -0,0 +1,3 @@
+from django.contrib.uploadprogress.middleware.uploaddb import FileProgressDB
+from django.contrib.uploadprogress.middleware.uploadcache import FileProgressCached
+
