Ticket #13614: 13614.filterselect-bug.3.diff

File 13614.filterselect-bug.3.diff, 16.6 KB (added by Julien Phalip, 7 years ago)
  • django/contrib/admin/static/admin/js/SelectFilter2.js

    diff --git a/django/contrib/admin/static/admin/js/SelectFilter2.js b/django/contrib/admin/static/admin/js/SelectFilter2.js
    index 0accd08..cf72679 100644
    a b  
    11/*
    22SelectFilter2 - Turns a multiple-select box into a filter interface.
    33
    4 Requires core.js, SelectBox.js and addevent.js.
     4Requires core.js and addevent.js.
    55*/
    66(function($) {
    77function findForm(node) {
    window.SelectFilter = { 
    1919            return;
    2020        }
    2121        var from_box = document.getElementById(field_id);
    22         from_box.id += '_from'; // change its ID
     22
     23        // --------------------------------------------------------------------
     24        // Below is a hack to get around a bug in jQuery, which has been fixed
     25        // so we could remove that hack once we upgrade jQuery.
     26        // See http://bugs.jquery.com/ticket/8129
     27        // The bug is that selected options aren't preserved after cloning the
     28        // select widget when simply doing `actual_box = $(from_box).clone()`.
     29        var actual_box = $('<select></select>').insertBefore(from_box).hide();
     30        var i;
     31        var attributes = from_box.attributes;
     32        for(i=0; i<attributes.length; i++) {
     33            actual_box.attr(attributes[i].name, attributes[i].value);
     34        }
     35        actual_box[0].innerHTML = from_box.innerHTML;
     36        $(from_box).find('option').each(function(index, option){
     37            $(option).data('index', index);
     38        });
     39        // --------------------------------------------------------------------
     40
     41        from_box.id += '_from';
     42        from_box.name += '_from';
    2343        from_box.className = 'filtered';
    2444
    2545        var ps = from_box.parentNode.getElementsByTagName('p');
    26         for (var i=0; i<ps.length; i++) {
     46        for (i=0; i<ps.length; i++) {
    2747            if (ps[i].className.indexOf("info") != -1) {
    2848                // Remove <p class="info">, because it just gets in the way.
    2949                from_box.parentNode.removeChild(ps[i]);
    window.SelectFilter = { 
    5878        filter_input.id = field_id + '_input';
    5979
    6080        selector_available.appendChild(from_box);
    61         var choose_all = quickElement('a', selector_available, gettext('Choose all'), 'title', interpolate(gettext('Click to choose all %s at once.'), [field_name]), 'href', 'javascript: (function(){ SelectBox.move_all("' + field_id + '_from", "' + field_id + '_to"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_add_all_link');
     81        var choose_all = quickElement('a', selector_available, gettext('Choose all'), 'title', interpolate(gettext('Click to choose all %s at once.'), [field_name]), 'href', 'javascript: (function(){ SelectFilter.move("'+ field_id + '", "' + field_id + '_from", "' + field_id + '_to", true);})()', 'id', field_id + '_add_all_link');
    6282        choose_all.className = 'selector-chooseall';
    6383
    6484        // <ul class="selector-chooser">
    6585        var selector_chooser = quickElement('ul', selector_div, '');
    6686        selector_chooser.className = 'selector-chooser';
    67         var add_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Choose'), 'title', gettext('Choose'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_from","' + field_id + '_to"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_add_link');
     87        var add_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Choose'), 'title', gettext('Choose'), 'href', 'javascript: (function(){ SelectFilter.move("'+ field_id + '", "'+ field_id + '_from", "' + field_id + '_to");})()', 'id', field_id + '_add_link');
    6888        add_link.className = 'selector-add';
    69         var remove_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Remove'), 'title', gettext('Remove'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_to","' + field_id + '_from"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_remove_link');
     89        var remove_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Remove'), 'title', gettext('Remove'), 'href', 'javascript: (function(){ SelectFilter.move("'+ field_id + '", "' + field_id + '_to", "' + field_id + '_from");})()', 'id', field_id + '_remove_link');
    7090        remove_link.className = 'selector-remove';
    7191
    7292        // <div class="selector-chosen">
    window.SelectFilter = { 
    7595        var title_chosen = quickElement('h2', selector_chosen, interpolate(gettext('Chosen %s') + ' ', [field_name]));
    7696        quickElement('img', title_chosen, '', 'src', admin_media_prefix + 'img/icon-unknown.gif', 'width', '10', 'height', '10', 'class', 'help help-tooltip', 'title', interpolate(gettext('This is the list of chosen %s. You may remove some by selecting them in the box below and then clicking the "Remove" arrow between the two boxes.'), [field_name]));
    7797
    78         var to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', 'multiple', 'size', from_box.size, 'name', from_box.getAttribute('name'));
     98        var to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', 'multiple', 'size', from_box.size, 'name', actual_box.attr('name') + '_to');
    7999        to_box.className = 'filtered';
    80         var clear_all = quickElement('a', selector_chosen, gettext('Remove all'), 'title', interpolate(gettext('Click to remove all chosen %s at once.'), [field_name]), 'href', 'javascript: (function() { SelectBox.move_all("' + field_id + '_to", "' + field_id + '_from"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_remove_all_link');
     100        var clear_all = quickElement('a', selector_chosen, gettext('Remove all'), 'title', interpolate(gettext('Click to remove all chosen %s at once.'), [field_name]), 'href', 'javascript: (function() { SelectFilter.move("'+ field_id + '", "' + field_id + '_to", "' + field_id + '_from", true);})()', 'id', field_id + '_remove_all_link');
    81101        clear_all.className = 'selector-clearall';
    82102
    83         from_box.setAttribute('name', from_box.getAttribute('name') + '_old');
    84 
    85103        // Set up the JavaScript event handlers for the select box filter interface
    86104        addEvent(filter_input, 'keyup', function(e) { SelectFilter.filter_key_up(e, field_id); });
    87105        addEvent(filter_input, 'keydown', function(e) { SelectFilter.filter_key_down(e, field_id); });
    88         addEvent(from_box, 'change', function(e) { SelectFilter.refresh_icons(field_id) });
    89         addEvent(to_box, 'change', function(e) { SelectFilter.refresh_icons(field_id) });
    90         addEvent(from_box, 'dblclick', function() { SelectBox.move(field_id + '_from', field_id + '_to'); SelectFilter.refresh_icons(field_id); });
    91         addEvent(to_box, 'dblclick', function() { SelectBox.move(field_id + '_to', field_id + '_from'); SelectFilter.refresh_icons(field_id); });
    92         addEvent(findForm(from_box), 'submit', function() { SelectBox.select_all(field_id + '_to'); });
    93         SelectBox.init(field_id + '_from');
    94         SelectBox.init(field_id + '_to');
    95         // Move selected from_box options to to_box
    96         SelectBox.move(field_id + '_from', field_id + '_to');
     106        addEvent(from_box, 'change', function(e) { SelectFilter.refresh_state(field_id) });
     107        addEvent(to_box, 'change', function(e) { SelectFilter.refresh_state(field_id) });
     108        addEvent(from_box, 'dblclick', function() {
     109            SelectFilter.move(field_id, field_id + '_from', field_id + '_to');
     110        });
     111        addEvent(to_box, 'dblclick', function() {
     112            SelectFilter.move(field_id, field_id + '_to', field_id + '_from');
     113        });
     114
     115        // Move selected from from_box options to to_box
     116        SelectFilter.move(field_id, field_id + '_from', field_id + '_to');
    97117
    98118        if (!is_stacked) {
    99119            // In horizontal mode, give the same height to the two boxes.
    window.SelectFilter = { 
    109129        }
    110130
    111131        // Initial icon refresh
    112         SelectFilter.refresh_icons(field_id);
     132        SelectFilter.refresh_state(field_id);
     133    },
     134    move: function(field_id, from_id, to_id, all) {
     135        var options;
     136        var from_box = $('#' + from_id);
     137        var to_box = $('#' + to_id);
     138        options = from_box.find("option");
     139        if (!all) {
     140            options = options.filter(':selected');
     141        }
     142        to_box.append(options);
     143        to_box.find('option').removeAttr('selected');
     144
     145        SelectFilter.refresh_state(field_id);
    113146    },
    114147    refresh_icons: function(field_id) {
    115148        var from = $('#' + field_id + '_from');
    window.SelectFilter = { 
    123156        $('#' + field_id + '_add_all_link').toggleClass('active', from.find('option').length > 0);
    124157        $('#' + field_id + '_remove_all_link').toggleClass('active', to.find('option').length > 0);
    125158    },
     159    refresh_state: function(field_id) {
     160        SelectFilter.refresh_icons(field_id);
     161
     162        // Re-generate the content of the actual, hidden, box.
     163        var actual_box = $('#' + field_id);
     164        var actual_options = actual_box.find('option');
     165        var to_box = $('#' + field_id + '_to');
     166
     167        // De-select all options.
     168        actual_box.find('option').removeAttr('selected', 'selected');
     169
     170        // Select all the same options as those from the 'to' box.
     171        to_box.find('option').each(function(){
     172            var option = actual_options[$(this).data('index')];
     173            $(option).attr('selected', 'selected');
     174        });
     175    },
    126176    filter_key_up: function(event, field_id) {
    127         var from = document.getElementById(field_id + '_from');
     177        var from_box = document.getElementById(field_id + '_from');
    128178        // don't submit form if user pressed Enter
    129179        if ((event.which && event.which == 13) || (event.keyCode && event.keyCode == 13)) {
    130             from.selectedIndex = 0;
    131             SelectBox.move(field_id + '_from', field_id + '_to');
    132             from.selectedIndex = 0;
     180            from_box.selectedIndex = 0;
     181            SelectFilter.move(field_id, field_id + '_from', field_id + '_to');
     182            from_box.selectedIndex = 0;
    133183            return false;
    134184        }
    135         var temp = from.selectedIndex;
    136         SelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value);
    137         from.selectedIndex = temp;
     185        var temp = from_box.selectedIndex;
     186// FIXME!
     187//        SelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value);
     188        from_box.selectedIndex = temp;
    138189        return true;
    139190    },
    140191    filter_key_down: function(event, field_id) {
    141         var from = document.getElementById(field_id + '_from');
     192        var from_box = document.getElementById(field_id + '_from');
    142193        // right arrow -- move across
    143194        if ((event.which && event.which == 39) || (event.keyCode && event.keyCode == 39)) {
    144             var old_index = from.selectedIndex;
    145             SelectBox.move(field_id + '_from', field_id + '_to');
    146             from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index;
     195            var old_index = from_box.selectedIndex;
     196            SelectFilter.move(field_id, field_id + '_from', field_id + '_to');
     197            from_box.selectedIndex = (old_index == from_box.length) ? from_box.length - 1 : old_index;
    147198            return false;
    148199        }
    149200        // down arrow -- wrap around
    150201        if ((event.which && event.which == 40) || (event.keyCode && event.keyCode == 40)) {
    151             from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1;
     202            from_box.selectedIndex = (from_box.length == from_box.selectedIndex + 1) ? 0 : from_box.selectedIndex + 1;
    152203        }
    153204        // up arrow -- wrap around
    154205        if ((event.which && event.which == 38) || (event.keyCode && event.keyCode == 38)) {
    155             from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1;
     206            from_box.selectedIndex = (from_box.selectedIndex == 0) ? from_box.length - 1 : from_box.selectedIndex - 1;
    156207        }
    157208        return true;
    158209    }
  • django/contrib/auth/admin.py

    diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py
    index f14b3d2..9d68ed8 100644
    a b csrf_protect_m = method_decorator(csrf_protect) 
    2121class GroupAdmin(admin.ModelAdmin):
    2222    search_fields = ('name',)
    2323    ordering = ('name',)
    24     filter_horizontal = ('permissions',)
     24    filter_vertical = ('permissions',)
    2525
    2626    def formfield_for_manytomany(self, db_field, request=None, **kwargs):
    2727        if db_field.name == 'permissions':
  • tests/regressiontests/admin_widgets/tests.py

    diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py
    index b0b3209..f0d948e 100644
    a b class HorizontalVerticalFilterSeleniumFirefoxTests(AdminSeleniumWebDriverTestCas 
    478478        self.cliff = models.Student.objects.create(name='Cliff')
    479479        self.arthur = models.Student.objects.create(name='Arthur')
    480480        self.school = models.School.objects.create(name='School of Awesome')
     481        self.school.students = [self.lisa, self.peter]
     482        self.school.alumni = [self.lisa, self.peter]
     483        self.school.save()
    481484        super(HorizontalVerticalFilterSeleniumFirefoxTests, self).setUp()
    482485
    483486    def execute_basic_operations(self, field_name, mode):
    class HorizontalVerticalFilterSeleniumFirefoxTests(AdminSeleniumWebDriverTestCas 
    505508            for option in self.selenium.find_elements_by_css_selector(from_box + ' option'):
    506509                option.click()
    507510            self.selenium.find_element_by_id(choose_link).click()
     511
    508512        self.assertSelectOptions(from_box, [])
    509513        self.assertSelectOptions(to_box,
    510514                        [str(self.lisa.id), str(self.peter.id),
    class HorizontalVerticalFilterSeleniumFirefoxTests(AdminSeleniumWebDriverTestCas 
    567571                         str(self.arthur.id), str(self.cliff.id)])
    568572
    569573    def test_basic(self):
    570         self.school.students = [self.lisa, self.peter]
    571         self.school.alumni = [self.lisa, self.peter]
    572         self.school.save()
     574        self.admin_login(username='super', password='secret', login_url='/')
     575        self.selenium.get(
     576            '%s%s' % (self.live_server_url, '/admin_widgets/school/%s/' % self.school.id))
    573577
     578        self.execute_basic_operations('students', 'vertical')
     579        self.execute_basic_operations('alumni', 'horizontal')
     580
     581        # Save and check that everything is properly stored in the database ---
     582        self.selenium.find_element_by_xpath('//input[@value="Save"]').click()
     583        self.school = models.School.objects.get(id=self.school.id) # Reload from database
     584        self.assertEqual(list(self.school.students.all()),
     585                         [self.arthur, self.cliff, self.jason, self.john])
     586        self.assertEqual(list(self.school.alumni.all()),
     587                         [self.arthur, self.cliff, self.jason, self.john])
     588
     589    def test_webkit_bug_13614(self):
    574590        self.admin_login(username='super', password='secret', login_url='/')
    575591        self.selenium.get(
    576592            '%s%s' % (self.live_server_url, '/admin_widgets/school/%s/' % self.school.id))
    577593
    578594        self.execute_basic_operations('students', 'vertical')
    579595        self.execute_basic_operations('alumni', 'horizontal')
     596        # Navigate away and go back to the change form page -------------------
     597        self.selenium.find_element_by_link_text('Home').click()
     598        self.selenium.back()
     599
     600        # Check that everything is still in place -----------------------------
     601        from_box = '#id_students_from'
     602        to_box = '#id_students_to'
     603        self.assertSelectOptions(from_box,
     604                        [str(self.bob.id), str(self.jenny.id),
     605                         str(self.lisa.id), str(self.peter.id)])
     606        self.assertSelectOptions(to_box,
     607                        [str(self.arthur.id), str(self.cliff.id),
     608                         str(self.jason.id), str(self.john.id)])
     609
     610        from_box = '#id_alumni_from'
     611        to_box = '#id_alumni_to'
     612        self.assertSelectOptions(from_box,
     613                        [str(self.bob.id), str(self.jenny.id),
     614                         str(self.lisa.id), str(self.peter.id)])
     615        self.assertSelectOptions(to_box,
     616                        [str(self.arthur.id), str(self.cliff.id),
     617                         str(self.jason.id), str(self.john.id)])
    580618
    581619        # Save and check that everything is properly stored in the database ---
    582620        self.selenium.find_element_by_xpath('//input[@value="Save"]').click()
Back to Top