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,172 @@
+
+var upload_progress_failures = 0;
+var TOTAL_UPLOAD_PROGRESS_FAILURES = 10;
+
+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.   */
+
+    progress_wrap = document.getElementById('progress_wrap');
+    upload_progress_failures++;
+    if (upload_progress_failures >= TOTAL_UPLOAD_PROGRESS_FAILURES){
+      window.clearTimeout(interval);
+    } 
+    progress_wrap.style.visibility = 'hidden';
+}
+
+var humanvalue = ['B','KB','MB','GB']
+function humanize(bytes) {
+  curbytes = bytes;
+  iterations = 0;
+  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", "/admin/upload_progress/", true);
+  req.onreadystatechange = function () {
+    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) {
+          progress_wrap = document.getElementById('progress_wrap');
+
+          if (!upload.state) {
+            progress_wrap.style.visibility = 'hidden';
+            return
+          } else if (upload.state == 'done') {
+             window.clearTimeout(interval);
+             progress_wrap.style.visibility = 'hidden';
+             return;   
+          } else {
+             progress_bar = document.getElementById('progress_bar');
+             bar_txt = document.getElementById('progress_text');
+
+             progress_wrap.style.visibility = 'visible';
+             bar_txt.innerHTML = ((upload.received / upload.size) * 100).toFixed(1)
+                     + '% - ' + humanize(upload.received) + ' of ' 
+                     + humanize(upload.size);
+             bar_width__px = 400 * upload.received / upload.size;
+             progress_bar.style.width = bar_width__px + 'px';
+          }
+        } else {
+          upload_ajax_problem();
+        }
+      } else {
+        upload_ajax_problem();
+      }
+    }
+  };
+  try {
+    req.setRequestHeader("X-Progress-Id", uuid);
+  } catch (e) {
+    /* couldn't set the header, the request is broken. */
+    req.abort();
+  }
+  req.send(null); 
+
+}
+
+
+function openprogress(e) {
+    upload_progress_failures = 0;
+    uuid = "";
+    for (i = 0; i < 32; i++) {
+        uuid += Math.floor(Math.random() * 16).toString(16);
+        }
+    frm = e.target||e.srcElement;
+
+    if (frm.action.indexOf('?') == -1) {
+       frm.action=frm.action+"?progress_id=" + uuid;
+    } else {
+       frm.action=frm.action+"&progress_id=" + uuid;
+    }
+
+    if (document.getElementById('progress_wrap')) {
+        document.getElementById('progress_wrap').style.visibility = 'hidden';
+        document.getElementById('progress_bar').style.width = '0';
+        document.getElementById('progress_text').innerHTML = '0%';
+
+        interval = window.setInterval(
+        function () {
+            fetch(uuid);
+            },
+        1000
+        );
+        return;
+    }
+
+    pos = getxy()
+    posx = parseInt((pos.x/2)-(420/2), 10)
+    posy = parseInt((pos.y/2)-(50/2), 10)
+
+    progress_wrap = quickElement('div', document.body, '', 'style', 
+        'position: absolute; top: '+posy+'px; left: '+posx+'px; height: 50px; ' +
+        'padding: 10px; width: 420px; background: #ffffff; ' +
+        'border: solid 1px #dddddd;' +
+        'visibility: hidden;', 'id', 'progress_wrap')
+
+    progress_label = quickElement('h1', progress_wrap, 'Upload progress')
+
+    progress = quickElement('div', progress_wrap, '', 'style', 
+        'top: 0; left: 0; width: 0px; ', 'id', 'progress_bar', 'class', 'submit-row')
+
+    progress_text = quickElement('div', progress_wrap, '0%', 'style',
+        'color: #000000; ', 'id', 'progress_text')
+    progress_wrap.style.visibility = 'hidden';
+    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 5126)
+++ 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 5126)
+++ django/contrib/admin/views/main.py	(working copy)
@@ -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):
+        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,16 @@
                                '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)
+
+    return HttpResponse(content=content, mimetype='text/plain')
