Code

Ticket #4165: 5343_javascript_and_admin.diff

File 5343_javascript_and_admin.diff, 15.9 KB (added by Michael Axiak <axiak@…>, 7 years ago)

New batch of JavaScript and Admin interaction

Line 
1Index: django/contrib/admin/media/js/UploadProgress.js
2===================================================================
3--- django/contrib/admin/media/js/UploadProgress.js     (revision 0)
4+++ django/contrib/admin/media/js/UploadProgress.js     (revision 0)
5@@ -0,0 +1,205 @@
6+
7+var upload_progress_failures = 0;
8+var TOTAL_UPLOAD_PROGRESS_FAILURES = 10;
9+var upload_progress_num__IE = 0;
10+var show_progress = false;
11+/* below is the url from admin to the upload progress.
12+   e.g. upload_progress/
13+*/
14+var progress_url  = "upload_progress/";
15+
16+var split_path = location.pathname.split('/');
17+
18+var new_array = new Array();
19+
20+var j=0;
21+for (var i=0; i<split_path.length; i++) {
22+    if (split_path[i] != '') {
23+        new_array[j] = split_path[i];
24+        j++;
25+    }
26+}
27+
28+admin_root = '/';
29+if (new_array.length > 3) {
30+    for (var i=0; i<new_array.length-3; i++) {
31+        admin_root += new_array[i]+'/';
32+    }
33+}
34+
35+progress_url = admin_root + progress_url;
36+
37+function getxy(){
38+    var x,y;
39+    if (self.innerHeight) // all except Explorer
40+        {
41+        x = self.innerWidth;
42+        y = self.innerHeight;
43+        }
44+    else if (document.documentElement && document.documentElement.clientHeight)
45+        // Explorer 6 Strict Mode
46+        {
47+        x = document.documentElement.clientWidth;
48+        y = document.documentElement.clientHeight;
49+        }
50+    else if (document.body) // other Explorers
51+        {
52+        x = document.body.clientWidth;
53+        y = document.body.clientHeight;
54+        }
55+    return {'x':x,'y':y}
56+    }
57+
58+
59+function upload_ajax_problem() {
60+    /* If there's a problem, will cancel after
61+       TOTAL_UPLOAD_PROGRESS_FAILURES tries.   */
62+
63+    var progress_wrap2 = document.getElementById('progress_wrap');
64+    upload_progress_failures++;
65+    if (upload_progress_failures >= TOTAL_UPLOAD_PROGRESS_FAILURES){
66+      window.clearTimeout(interval);
67+    }
68+    progress_wrap2.style.display = 'none';
69+    progress_wrap2.style.visibility = 'hidden';
70+}
71+
72+var humanvalue = ['B','KB','MB','GB']
73+function humanize(bytes) {
74+  curbytes = bytes;
75+  iterations = 0;
76+  if (!curbytes) {
77+     return '';
78+  }
79+  while (curbytes>1024) {
80+    iterations++;
81+    curbytes=curbytes/1024;
82+  }
83+  return curbytes.toFixed(1) + ' ' + humanvalue[iterations];
84+}
85+
86+interval = null;
87+function fetch(uuid) {
88+  /* no ajax here */
89+  if (!xmlhttp) {
90+    upload_ajax_problem();
91+    return;
92+  }
93+
94+  req = xmlhttp;
95+  req.open("GET", progress_url+"?" + upload_progress_num__IE, true);
96+  upload_progress_num__IE++; // IE Hack
97+  req.onreadystatechange = function () {
98+    var progress_wrap2 = document.getElementById('progress_wrap');
99+    var progress_bar2  = document.getElementById('progress_bar');
100+    var bar_txt = document.getElementById('progress_text');
101+
102+    if (req.readyState == 4) {
103+      try {
104+        request_status = req.status;
105+      } catch (e) {
106+        /* Really bad. */
107+        request_status = -1;
108+      }
109+      if (request_status == 200) {
110+        var upload = new Function(" return "+req.responseText)();
111+        if (upload) {
112+
113+          if (!upload.state) {
114+             progress_wrap2.style.visibility = 'hidden';
115+             progress_wrap2.style.display    = 'none';
116+             return;
117+          } else if (upload.state == 'done') {
118+             window.clearTimeout(interval);
119+             progress_wrap2.style.visibility = 'hidden';
120+             progress_wrap2.style.display    = 'none';
121+             return;   
122+          } else {
123+             if (show_progress) {
124+                 progress_wrap2.style.visibility = 'visible';
125+                 progress_wrap2.style.display    = 'block';
126+             }
127+             move_to_center(progress_wrap2);
128+             bar_txt.innerHTML = ((upload.received / upload.size) * 100).toFixed(1)
129+                     + '% - ' + humanize(upload.received) + ' of '
130+                     + humanize(upload.size);
131+             var bar_width__px = 400 * upload.received / upload.size;
132+             progress_bar2.style.width = bar_width__px + 'px';
133+          }
134+        } else {
135+          upload_ajax_problem();
136+        }
137+      } else {
138+        upload_ajax_problem();
139+      }
140+    }
141+  };
142+  try {
143+    req.setRequestHeader("X-Upload-Id", uuid);
144+  } catch (e) {
145+    /* couldn't set the header, the request is broken. */
146+    req.abort();
147+  }
148+  req.send(null);
149+
150+}
151+
152+function move_to_center(progress_wrap) {
153+    pos = getxy();
154+    posx = parseInt((pos.x/2)-(420/2), 10);
155+    posy = parseInt((pos.y/2)-(50/2), 10);
156+
157+    progress_wrap.style.top  = posy + 'px';
158+    progress_wrap.style.left = posx + 'px';   
159+}
160+
161+function close_progress() {
162+    var progress_wrap2 = document.getElementById('progress_wrap');
163+    progress_wrap2.style.display = 'none';
164+    progress_wrap2.style.visibility = 'hidden';
165+    show_progress = false;
166+    // don't want to follow a link.
167+    return false;
168+}
169+
170+
171+function openprogress(e) {
172+
173+    show_progress = true;
174+    upload_progress_failures = 0;
175+    uuid = "";
176+    for (i = 0; i < 32; i++) {
177+        uuid += Math.floor(Math.random() * 16).toString(16);
178+        }
179+    frm = e.target||e.srcElement;
180+
181+    /*
182+    var progress_wrap2 = document.getElementById('progress_wrap');
183+    move_to_center(progress_wrap2);
184+    progress_wrap2.style.display = 'block';
185+    progress_wrap2.style.visibility = 'visible';
186+    */
187+
188+    if (frm.action.indexOf('?') == -1) {
189+       frm.action=frm.action+"?progress_id=" + uuid
190+    } else {
191+       frm.action=frm.action+"&progress_id=" + uuid;
192+    }
193+
194+    interval = window.setInterval(
195+        function () {
196+            fetch(uuid);
197+            },
198+        1000
199+        );
200+}
201+
202+addEvent(window, 'load', function() {
203+        frms = document.getElementsByTagName('form');
204+        for (var i=0; i<frms.length; i++) {
205+           if (frms[i].encoding.toLowerCase() == 'multipart/form-data') {
206+              addEvent(frms[i], 'submit',  openprogress);
207+              return;
208+           }
209+        }
210+    });
211Index: django/contrib/admin/urls.py
212===================================================================
213--- django/contrib/admin/urls.py        (revision 5343)
214+++ django/contrib/admin/urls.py        (working copy)
215@@ -10,6 +10,7 @@
216     ('^$', 'django.contrib.admin.views.main.index'),
217     ('^r/(\d+)/(.*)/$', 'django.views.defaults.shortcut'),
218     ('^jsi18n/$', i18n_view, {'packages': 'django.conf'}),
219+    ('^upload_progress/$', 'django.contrib.admin.views.main.upload_progress'),
220     ('^logout/$', 'django.contrib.auth.views.logout'),
221     ('^password_change/$', 'django.contrib.auth.views.password_change'),
222     ('^password_change/done/$', 'django.contrib.auth.views.password_change_done'),
223Index: django/contrib/admin/views/main.py
224===================================================================
225--- django/contrib/admin/views/main.py  (revision 5343)
226+++ django/contrib/admin/views/main.py  (working copy)
227@@ -9,7 +9,7 @@
228 from django.shortcuts import get_object_or_404, render_to_response
229 from django.db import models
230 from django.db.models.query import handle_legacy_orderlist, QuerySet
231-from django.http import Http404, HttpResponse, HttpResponseRedirect
232+from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseServerError
233 from django.utils.html import escape
234 from django.utils.text import capfirst, get_text_list
235 import operator
236@@ -81,6 +81,8 @@
237 def get_javascript_imports(opts, auto_populated_fields, field_sets):
238 # Put in any necessary JavaScript imports.
239     js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
240+    if opts.has_field_type(models.FileField) and settings.FILE_UPLOAD_DIR:
241+        js.append('js/UploadProgress.js')
242     if auto_populated_fields:
243         js.append('js/urlify.js')
244     if opts.has_field_type(models.DateTimeField) or opts.has_field_type(models.TimeField) or opts.has_field_type(models.DateField):
245@@ -777,3 +779,19 @@
246                                'admin/%s/change_list.html' % app_label,
247                                'admin/change_list.html'], context_instance=c)
248 change_list = staff_member_required(never_cache(change_list))
249+
250+def upload_progress(request):
251+    """
252+    Given this request, returns a JSON
253+    object that has information on a file upload progress.
254+    If there is no file upload in progress, returns an
255+    empty dictionary, '{}'.
256+    """
257+    from django.utils import simplejson
258+
259+    content = simplejson.dumps(request.file_progress)
260+
261+    if content.strip() == '{}':
262+        return HttpResponseServerError('')
263+    else:
264+        return HttpResponse(content=content, mimetype='text/plain')
265Index: django/contrib/admin/templates/admin/change_form.html
266===================================================================
267--- django/contrib/admin/templates/admin/change_form.html       (revision 5343)
268+++ django/contrib/admin/templates/admin/change_form.html       (working copy)
269@@ -65,6 +65,18 @@
270    {% auto_populated_field_script auto_populated_fields change %}
271    </script>
272 {% endif %}
273+
274+{% if has_file_field %}
275+<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;">
276+   <a href="#" onclick="close_progress();return false" title="Close Progress Bar"
277+      style="color: #c00; font-size: 1.5em; font-weight: bold; float: right; padding: 0; position: relative; top: -2px; left: -2px;">X</a>
278+   <h1>Upload progress</h1>
279+
280+   <div id="progress_bar" style="top: 0; left: 0; width: 0; z-index: 9049; height: 4px;" class="submit-row"></div>
281+   <div id="progress_text" style="color: black;">0%</div>
282 </div>
283+{% endif %}
284+
285+</div>
286 </form></div>
287 {% endblock %}
288Index: django/contrib/uploadprogress/models.py
289===================================================================
290--- django/contrib/uploadprogress/models.py     (revision 0)
291+++ django/contrib/uploadprogress/models.py     (revision 0)
292@@ -0,0 +1,77 @@
293+"""
294+Models file for a simple file upload progress application.
295+This cause the file progress to be stored in the database.
296+To activate:
297+1) Add 'django.contrib.uploadprogress.middleware.FileProgressDB'
298+   to your MIDDLEWARE_CLASSES setting.
299+2) Add 'django.contrib.uploadprogress' to your INSTALLED_APPS.
300+
301+"""
302+
303+from django.db import models
304+import datetime
305+
306+try:
307+    import cPickle as pickle
308+except ImportError:
309+    import pickle
310+
311+class FileProgress(models.Model):
312+
313+    remote_addr = models.CharField(maxlength=64)
314+    progress_id = models.CharField(maxlength=32)
315+    progress_text = models.TextField(editable = False)
316+    last_ts     = models.DateTimeField()
317+
318+    class Meta:
319+        verbose_name_plural = 'File Progresses'
320+        unique_together = (('remote_addr',
321+                           'progress_id',
322+                           ),
323+                           )
324+
325+    class Admin:
326+        pass
327+
328+    def __str__(self):
329+        return 'File Progress for "%s" and IP "%s".' % (self.progress_id, self.remote_addr)
330+
331+    def _get_dict(self):
332+        """
333+        Returns a dictionary object.
334+        """
335+        if hasattr(self, '_progress_dict'):
336+            return self._progress_dict
337+       
338+        if not self.progress_text:
339+            self._progress_dict = {}
340+            return {}
341+
342+        try:
343+            self._progress_dict = pickle.loads(self.progress_text)
344+        except:
345+            self._progress_dict = {}
346+
347+        return self._progress_dict
348+
349+    def _set_dict(self, dict):
350+        """
351+        Sets a dictionary object.
352+        """
353+        self._progress_dict = dict
354+
355+    progress = property(_get_dict, _set_dict)
356+
357+    def save(self, *args, **kwargs):
358+        """
359+        Pickles the dictionary representation
360+        of the progress.
361+        """
362+        try:
363+            self.progress_text = pickle.dumps(self.progress)
364+        except:
365+            pass
366+
367+        self.last_ts = datetime.datetime.now()
368+
369+        return super(FileProgress, self).save(*args, **kwargs)
370Index: django/contrib/uploadprogress/middleware/uploadcache.py
371===================================================================
372--- django/contrib/uploadprogress/middleware/uploadcache.py     (revision 0)
373+++ django/contrib/uploadprogress/middleware/uploadcache.py     (revision 0)
374@@ -0,0 +1,59 @@
375+"""
376+
377+Middleware to track file progress using the cache framework.
378+To use, just add
379+    'django.contrib.uploadprogress.middleware.FileProgressCached'
380+to your ``MIDDLEWARE_SETTINGS``.
381+
382+If your cache framework does not work, this will not work either.
383+
384+"""
385+from django.core.cache import cache
386+from django.conf import settings
387+from django.http.multipartparser import MultiPartParserError
388+
389+UPLOAD_CACHE_PREFIX = getattr(settings, 'UPLOAD_CACHE_PREFIX', 'UPLOAD_PROGRESS_')
390+
391+class FileProgressStore(object):
392+
393+    def _get_key(self, request):
394+        """
395+        Returns the cache prefix for any cache key.
396+        Uses the IP Address as well as the randomly generated uuid.
397+        """
398+       
399+        if hasattr(self, '_cache_key'):
400+            return self._cache_key
401+       
402+        self._cache_key = '%s__%s__%s' % \
403+                           (UPLOAD_CACHE_PREFIX,
404+                            request.META['REMOTE_ADDR'],
405+                            request.META['UPLOAD_PROGRESS_ID'],)
406+
407+        return self._cache_key
408+
409+    def __get__(self, request, HttpRequest):
410+        return cache.get(self._get_key(request), {})
411+
412+    def __set__(self, request, new_val):
413+        received_size = total_size = -1
414+        try:
415+            total_size = int(new_val['size'])
416+        except:
417+            pass
418+
419+        try:
420+            received_size = int(new_val['received'])
421+        except:
422+            pass
423+       
424+        cache.set(self._get_key(request), new_val)
425+
426+    def __delete__(self, request):
427+        cache.delete(self._get_key(request))
428+
429+class FileProgressCached(object):
430+
431+    def process_request(self, request):
432+        # set the request.file_progress descriptor
433+        request.__class__.file_progress = FileProgressStore()
434Index: django/contrib/uploadprogress/middleware/uploaddb.py
435===================================================================
436--- django/contrib/uploadprogress/middleware/uploaddb.py        (revision 0)
437+++ django/contrib/uploadprogress/middleware/uploaddb.py        (revision 0)
438@@ -0,0 +1,47 @@
439+"""
440+
441+Middleware to hold fileupload progress state in a database.
442+To install, simply add
443+    'django.contrib.uploadprogress.middleware.FileProgressDB'
444+   
445+to your ``MIDDLEWARE_SETTINGS`` and
446+    'django.contrib.uploadprogress'
447+to your ``INSTALLED_APPS``.
448+
449+Then run ``./manage.py syncdb`` as you normally would.
450+
451+"""
452+from django.contrib.uploadprogress.models import FileProgress
453+from django.http.multipartparser import MultiPartParserError
454+from django.conf import settings
455+
456+try:
457+    import cPickle as pickle
458+except ImportError:
459+    import pickle
460+
461+class FileProgressDBStore(object):
462+
463+    def _get_db_row(self, request):
464+        if not hasattr(self, '_db_row'):
465+            self._db_row, created = FileProgress.objects.get_or_create(
466+                                                 remote_addr = request.META['REMOTE_ADDR'],
467+                                                 progress_id = request.META['UPLOAD_PROGRESS_ID'])
468+
469+        return self._db_row
470+
471+    def __get__(self, request, HttpRequest):
472+        return self._get_db_row(request).progress
473+
474+    def __set__(self, request, new_val):
475+        row = self._get_db_row(request)
476+        row.progress = new_val
477+        row.save()
478+
479+    def __delete__(self, request):
480+        self._get_db_row(request).delete()
481+
482+class FileProgressDB(object):
483+
484+    def process_request(self, request):
485+        request.__class__.file_progress = FileProgressDBStore()
486Index: django/contrib/uploadprogress/middleware/__init__.py
487===================================================================
488--- django/contrib/uploadprogress/middleware/__init__.py        (revision 0)
489+++ django/contrib/uploadprogress/middleware/__init__.py        (revision 0)
490@@ -0,0 +1,3 @@
491+from django.contrib.uploadprogress.middleware.uploaddb import FileProgressDB
492+from django.contrib.uploadprogress.middleware.uploadcache import FileProgressCached
493+