Ticket #13: inline-tabular.diff

File inline-tabular.diff, 11.8 KB (added by barbuza, 9 years ago)

dynamic adding and ajax deleting for tabular mode. to enable, add js to Media class in ModelAdmin of your models

  • django/contrib/admin/media/css/global.css

     
    139139/* OBJECT HISTORY */
    140140table#change-history { width:100%; }
    141141table#change-history tbody th { width:16em; }
     142
     143/* INLINE DELETING */
     144td.delete { width: 20px; }
     145#ajax-indicator { position: absolute; top: 0px; right: 0px; padding: 5px 10px; border: 1px solid #ddd; background: #ffc; }
  • django/contrib/admin/media/js/admin/InlineObjectsTabular.js

     
     1(function() {
     2
     3    if (! gettext) gettext = function(x) { return x}
     4
     5    String.prototype.trim = function() {
     6        return this.replace(/^\s+|\s+$/g, '');
     7    }
     8
     9    Array.prototype.filter = function(fn) {
     10        var results = [];
     11                for (var i = 0, l = this.length; i < l; i++){
     12                        if (fn(this[i])) results.push(this[i]);
     13                }
     14                return results;
     15    }
     16
     17    Array.prototype.map = function(fn) {
     18        var results = [];
     19                for (var i = 0, l = this.length; i < l; i++){
     20                        results.push(fn(this[i]));
     21                }
     22                return results;
     23    }
     24
     25        Array.prototype.flatten = function(){
     26                var array = [];
     27                for (var i = 0, l = this.length; i < l; i++){
     28                        array = array.concat(this[i]);
     29                }
     30                return array;
     31        }
     32
     33    function $A(e) {
     34        var r = [];
     35        for(var i=0, l=e.length; i<l; i++) r.push(e[i]);
     36        return r;
     37    }
     38
     39    function _prepare_selector(selector, point) {
     40        var queue = selector.split(/\s/).map(function(x){return x.trim()});
     41        var points = [point];
     42        for(var i=0, l=queue.length; i<l; i++) {
     43            var new_points = [];
     44            for(var j=0, k=points.length; j<k; j++) {
     45                var tmp = _select(queue[i], points[j]);
     46                if (!tmp) break;
     47                for(var p=0, o=tmp.length; p<o; p++) {
     48                    new_points.push(tmp[p]);
     49                }
     50            }
     51            points = new_points;
     52        }
     53        return points;
     54    }
     55   
     56    function _select(selector, point) {
     57        var chunks = selector.split(/[\.]/);
     58        var splitters = selector.match(/[\.]/g);
     59        if (splitters) {
     60            if(chunks.length > splitters.length){
     61                splitters.unshift(null);
     62            }
     63            var f_ch = chunks.shift(), f_sel = splitters.shift();
     64            if (f_sel == '.') {
     65                els=point.getElementsByClassName(f_ch);
     66            } else if (f_sel == null) {
     67                els = point.getElementsByTagName(f_ch);
     68            }
     69            els = $A(els);
     70            for(var i=0, l=chunks.length; i<l; i++){
     71                var ch = chunks[i], sel = splitters[i], tmp = [];
     72               
     73                for(var j=0, k=els.length; j<k; j++){
     74                    if(els[j].className.match(ch)) tmp.push(els[j]);
     75                }
     76                els = tmp;
     77            }
     78            return $A(els);
     79           
     80        } else {
     81            return $A(point.getElementsByTagName(chunks.shift()));
     82        }
     83    }
     84   
     85    function show_loading() {
     86        hide_loading();
     87        var l = document.createElement('div');
     88        l.appendChild(document.createTextNode(gettext('loading ...')));
     89        l.id = 'ajax-indicator';
     90        document.body.appendChild(l);
     91    }
     92   
     93    function hide_loading() {
     94        var l = $('ajax-indicator');
     95        if(l) l.parentNode.removeChild(l);
     96    }
     97   
     98    function $(e) {
     99        return document.getElementById(e);
     100    }
     101   
     102    function $$(e, point) {
     103        point = point ? point : document;
     104        return e.split(',')
     105            .map(function(x){return x.trim()})
     106            .filter(function(x){return !!x})
     107            .map(function(x){return _prepare_selector(x, point)})
     108            .flatten();
     109    }
     110
     111    function make_delete_handle(h) {
     112        var p = h.parentNode;
     113        p.removeChild(h);
     114        var origin = $$('td.original input', p.parentNode)[0];
     115        if (! origin) return;
     116        var id = origin.value, name = origin.name.match(/^(\w+)-/i)[1];
     117        var d_handle = document.createElement('span');
     118        d_handle.className = 'inline-deletelink';
     119        addEvent(d_handle, 'click', function(){
     120            show_loading();
     121            xmlhttp.open('POST', 'delete-inline/'+name+'/'+id+'/', true);
     122            function onStateChange() {
     123                if (xmlhttp.readyState != 4) return;
     124                hide_loading();
     125                if ((xmlhttp.status >= 200) && (xmlhttp.status < 300)){
     126                    var response;
     127                    eval('response = '+xmlhttp.responseText);
     128                    if(response.error) {
     129                        alert(response.error);
     130                        return;
     131                    }
     132                    var tc = $('id_'+name+'-TOTAL_FORMS');
     133                    tc.value = Math.ceil(tc.value) - 1;
     134                    var ic = $('id_'+name+'-INITIAL_FORMS');
     135                    ic.value = Math.ceil(ic.value) - 1;
     136                    var tbody = p.parentNode.parentNode;
     137                    p.parentNode.parentNode.removeChild(p.parentNode);
     138                    var trs = $$('tr', tbody);
     139                    for(var i=0, l=trs.length; i<l; i++){
     140                        trs[i].className = trs[i].className.replace(/row\d/, i % 2 ? 'row2' : 'row1');
     141                        function fix_id_and_name(input) {
     142                            input.name = input.name.replace(/-\d-/, '-'+i+'-');
     143                            input.id = input.id.replace(/-\d-/, '-'+i+'-');
     144                        }
     145                        $$('input', trs[i]).map(fix_id_and_name);
     146                    }
     147                   
     148                } else {
     149                    // strange
     150                }
     151            }
     152            xmlhttp.onreadystatechange = onStateChange;
     153            xmlhttp.send(null);
     154        })
     155        p.appendChild(d_handle);
     156    }
     157
     158    function make_add_link(group){
     159       
     160        var rows = $$('tr', group);
     161        var origin = $$('td.original input', group)[0];
     162        if(! origin) return;
     163        var name = origin.name.match(/^(\w+)-/i)[1];
     164        var tc = $('id_'+name+'-TOTAL_FORMS');
     165        last_row = rows[rows.length-1];
     166        var tmp_row = document.createElement('tr');
     167        var tds = $$('td', last_row);
     168        for(var i=0, l=tds.length; i<l; i++){
     169            var td = tmp_row.appendChild(document.createElement('td'));
     170            td.className = tds[i].className;
     171            var inputs = $$('input, textarea', tds[i]);
     172            for(var j=0, k=inputs.length; j<k; j++){
     173                var o_input = inputs[j];           
     174                var input = td.appendChild(document.createElement(o_input.nodeName));
     175                input.type = o_input.type;
     176                input.id = o_input.id.replace(/-\d-/, '---');
     177                input.name = o_input.name.replace(/-\d-/, '---');
     178            }
     179        }
     180       
     181        function add_new() {
     182            var tbody = $$('tbody', group)[0];
     183            var tr = document.createElement('tr');
     184            var tds = $$('td', tmp_row);
     185            tc.value = Math.ceil(tc.value) + 1;
     186            var id = tc.value - 1;
     187            for(var i=0, l=tds.length; i<l; i++){
     188                var td = tr.appendChild(document.createElement('td'));
     189                td.className = tds[i].className;
     190                var inputs = $$('input, textarea', tds[i]);
     191                for(var j=0, k=inputs.length; j<k; j++){
     192                    var o_input = inputs[j];
     193                    var input = td.appendChild(document.createElement(o_input.nodeName));
     194                    input.type = o_input.type;
     195                    input.id = o_input.id.replace(/---/, '-'+id+'-');
     196                    input.name = o_input.name.replace(/---/, '-'+id+'-');
     197                }
     198            }
     199            tr.className = id % 2 ? 'row2' : 'row1';
     200            tbody.appendChild(tr);
     201        }
     202       
     203        var link = document.createElement('a');
     204        link.className = 'addlink';
     205        link.href = 'javascript:(function(){})()';
     206        link.appendChild(document.createTextNode(gettext('Add another')));
     207        addEvent(link, 'click', add_new);
     208        group.appendChild(link);
     209       
     210        tc.value = $$('tr.row1, tr.row2', group).length;
     211    }
     212
     213    function init_tabular_deleting() {
     214        var trs = $$('div.inline-related.tabular thead tr');
     215        for(var i=0, l=trs.length; i<l; i++) {
     216            var ths = $$('th', trs[i]);
     217            ths[ths.length-1].firstChild.data = '';
     218            ths[ths.length-1].style.borderLeft = 'none';
     219        }
     220        $$('div.inline-related.tabular').map(make_add_link);
     221        $$('div.inline-related.tabular td.delete input').map(make_delete_handle);
     222    }
     223
     224    addEvent(window, 'load', init_tabular_deleting);
     225
     226})();
     227 No newline at end of file
  • django/contrib/admin/options.py

     
    248248            return self.history_view(request, unquote(url[:-8]))
    249249        elif url.endswith('delete'):
    250250            return self.delete_view(request, unquote(url[:-7]))
     251        elif url.count('delete-inline'):
     252            return self.delete_inline_view(request, unquote(url))
    251253        else:
    252254            return self.change_view(request, unquote(url))
    253255
     
    667669            "admin/delete_confirmation.html"
    668670        ], context, context_instance=template.RequestContext(request))
    669671
     672    def delete_inline_view(self, request, rest, extra_context=None):
     673        """ 'delete-inline' view for this model """
     674       
     675        from django.http import HttpResponse
     676        from django.utils import simplejson
     677        from django.core.exceptions import ObjectDoesNotExist
     678       
     679        def json(data):
     680            return HttpResponse(simplejson.dumps(data), mimetype='text/x-json')
     681       
     682        try:
     683            parent_id, trash, set_name, child_id = filter(None, rest.split('/'))
     684        except ValueError:
     685            return json({'error': _('Invalid request.')})
     686       
     687        if request.method != 'POST':
     688            return json({'error': _('Request method should be post.')})
     689       
     690        from django.contrib.admin.models import LogEntry, DELETION
     691        opts = self.model._meta
     692        app_label = opts.app_label
     693
     694        try:
     695            parent = self.model._default_manager.get(pk=parent_id)
     696        except self.model.DoesNotExist:
     697            parent = None
     698
     699        try:
     700            _set = getattr(parent, set_name)
     701        except AttributeError:
     702            return json({'error': _('Unknown "%s" for "%s".') % (set_name, self.model)})
     703
     704        try:
     705            obj = _set.get(pk=child_id)
     706        except ObjectDoesNotExist:
     707            obj = None
     708           
     709        if obj is None:
     710            return json({'error': _('Object does not exists.')})
     711               
     712        perms_needed = sets.Set()
     713        get_deleted_objects([], perms_needed, request.user, obj, obj._meta, 1, self.admin_site)
     714       
     715        if perms_needed:
     716            return json({'error': _('Not enought permissions to delete related objects.')})
     717       
     718        obj.delete()
     719        LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(obj.__class__).id, obj, str(obj), DELETION)
     720
     721        return json({'error': None})
     722
    670723    def history_view(self, request, object_id, extra_context=None):
    671724        "The 'history' admin view for this model."
    672725        from django.contrib.admin.models import LogEntry
Back to Top