abyx
abyx

Reputation: 72808

Minimize the list filter in django-admin

I quite like the filter feature of django admin views (list_filter).

But, on views with a lot of fields, I would really like the ability to minimize/expand it with a click, to save screen real-estate and also because it sometimes actually hides stuff.

Is there an easy way to add a collapse button (some already existing plugin I haven't found or something similar)?

Upvotes: 15

Views: 9603

Answers (11)

Kound
Kound

Reputation: 2531

For some reason the the Javascripts didn't work with Django 5. I ended up overwriting the template with some custom CSS, which works just fine, by adopting the changelists template. You could properly also add only a custom CSS file inside the media.

{% extends "admin/change_list.html" %}

{% block header %}
    {{ block.super }}

    <style>

        /* make changelist filter small by default */
        #changelist-filter {
          position: absolute;
          display: block;
          right: 0;
          border: solid rgba(255, 0, 0, 0.2) 0.5px;
          width: 8em;
          height: 2em;
        }
        /* prevent inner elements to be visible */
        #changelist-filter * {
          height: 0;
          display: none;
        }
        /* but show the filter header */
        #changelist-filter-header {
          height: auto;
          display: block;
        }
        /* when hovering, recover size again */
        #changelist-filter:hover {
          height: auto;
          width: auto;
        }
        /* and display the inner content */
        #changelist-filter:hover * {
          height: auto;
          display: block;
        }

    </style>
{% endblock %}

No hover view:

No Hover View

Hover view as normal:

Hover view

Upvotes: 0

AndyC
AndyC

Reputation: 129

Django 4.x, here is how I do.

  1. create admin template as below

    {% extends "admin/change_list.html" %} {% block extrastyle %} {{ block.super }}

    function toggle_filter() { $("#changelist-filter").toggle("slow"); }; $(document).ready(function(){ // close them by default $("#changelist-filter").toggle("fast"); }); {% endblock %}
  2. enhance admin/base_site.html to add button

<button onclick="toggle_filter()" class="btn btn-warning btn-sm" type="submit">Toggle Filter</button>

Upvotes: 1

Giuseppe De Marco's snippet works best. So i am adding his code snippet here for easy access. It even solves the problem (Cons) discussed above by joelg:

// Copied from 
// https://github.com/peppelinux/Django-snippets/tree/master/django-admin.js-snippets

(function($){
    
    var element_2_collapse = '#changelist-filter';
    var element_head       = 'h2'
    var filter_title       = 'h3'
    
    // this is needed for full table resize after filter menu collapse
    var change_list        = '#changelist'
    
    
    ListFilterCollapsePrototype = {
        bindToggle: function(){
            var that = this;
            this.$filterTitle.click(function(){
                
                // check if some ul is collapsed
                // open it before slidetoggle all together
                $(element_2_collapse).children('ul').each(function(){
                    if($(this).is(":hidden"))
                        {
                            $(this).slideToggle();
                        }            
                })
                
                // and now slidetoggle all 
                that.$filterContentTitle.slideToggle();
                that.$filterContentElements.slideToggle();            
                that.$list.toggleClass('filtered');
    
            });
    
        },
        init: function(filterEl) {
            this.$filterTitle = $(filterEl).children(element_head);
            this.$filterContentTitle = $(filterEl).children(filter_title);
            this.$filterContentElements = $(filterEl).children('ul');
            $(this.$filterTitle).css('cursor', 'pointer');
            this.$list = $(change_list );
            
            // header collapse
            this.bindToggle();
        
            // collapsable childrens 
            $(element_2_collapse).children(filter_title).each(function(){
                var $title = $(this);
                $title.click(function(){
                    $title.next().slideToggle();
                            
                });
            
            $title.css('border-bottom', '1px solid grey');
            $title.css('padding-bottom', '5px');
            $title.css('cursor', 'pointer');     
            
            });
        
        
            
        }
    }
    function ListFilterCollapse(filterEl) {
        this.init(filterEl);
    }
    ListFilterCollapse.prototype = ListFilterCollapsePrototype;
    
    $(document).ready(function(){
        $(element_2_collapse).each(function(){
            var collapser = new ListFilterCollapse(this);
        });
        
        // close them by default
        $(element_2_collapse+' '+element_head).click()
        
        // if some filter was clicked it will be visible for first run only
        // selezione diverse da Default
        
    
        $(element_2_collapse).children(filter_title).each(function(){
            
            lis = $(this).next().children('li')
            lis.each(function(cnt) {
              if (cnt > 0)
               {
                if ($(this).hasClass('selected')) {
                    $(this).parent().slideDown(); 
                    $(this).parent().prev().slideDown();
                    
                    // if some filters is active every filters title (h3) 
                    // should be visible
                    $(element_2_collapse).children(filter_title).each(function(){
                        $(this).slideDown(); 
                    })
                    
                    $(change_list).toggleClass('filtered');
                    
                }
               }
            })
    
        });
    
    });
    })(django.jQuery);

Upvotes: 1

Giuseppe De Marco
Giuseppe De Marco

Reputation: 701

I wrote a snippets for menu collapse and single element menu collapse.

It's a fork from abyx code, I've just extended it.

If a filter was previously activated the element menu related to this will start as opened.

The filter menu starts closed as default. Hope this helps

https://github.com/peppelinux/Django-snippets/tree/master/django-admin.js-snippets

Upvotes: 1

joelg
joelg

Reputation: 46

Combined Tim's and maGo's approaches, with some tweaks:

Pros:

  • Allows user to hide the entire list (added "click to hide/unhide" to the filter list title so user knows what to do).
  • Maintains folded filter categories by default

Cons:

  • The page refresh after a filter is selected causes the filter categories to fold once again; ideally the ones you're working with would stay open.

The code:

(function($){ $(document).ready(function(){

    // Start with a filter list showing only its h3 subtitles; clicking on any
    // displays that filter's content; clicking again collapses the list:
    $('#changelist-filter > h3').each(function(){
        var $title = $(this);
        $title.next().toggle();
        $title.css("cursor","pointer");
        $title.click(function(){
            $title.next().slideToggle();
        });
    });

    // Add help after title:
    $('#changelist-filter > h2').append("<span style='font-size: 80%; color: grey;'> (click to hide/unhide)</span>");

    // Make title clickable to hide entire filter:
    var toggle_flag = true;
    $('#changelist-filter > h2').click(function () {
        toggle_flag = ! toggle_flag;
        $('#changelist-filter').find('> h3').each(function(){
                $(this).toggle(toggle_flag);
        });
    });
  });
})(django.jQuery);

Upvotes: 2

maGo
maGo

Reputation: 362

Modified fanlix solution to:

  1. Show cursor as pointer on hover
  2. Be folded by default

Code

(function($){ $(document).ready(function(){
    $('#changelist-filter > h3').each(function(){
        var $title = $(this);
        $title.next().toggle();
        $title.css("cursor","pointer");
        $title.click(function(){
            $title.next().slideToggle();
        });
    });
    var toggle_flag = false;
    $('#changelist-filter > h2').css("cursor","pointer");
    $('#changelist-filter > h2').click(function () {
        toggle_flag = ! toggle_flag;
        $('#changelist-filter > ul').each(function(){
            $(this).slideToggle(toggle_flag);
        });
    });
  }); 
})(django.jQuery);

Upvotes: 2

Stan
Stan

Reputation: 8976

I have written a small snippets downloadable on bitbucket for the purpose.

The state of the filters are stored in a cookie and the selected filters stay visible.

enter image description here

Upvotes: 7

Tim
Tim

Reputation: 41

Made another change to this so that the H3's are hidden, as well as the filter lists, when you click on the top H2. This will get the entire list of filters out of the way if you click on the top "Filters".

This is the js file content

;(function($){ $(document).ready(function(){
    $('#changelist-filter > h3').each(function(){
        var $title = $(this);
        $title.click(function(){
            $title.next().slideToggle();
        });
    });
    var toggle_flag = true;
    $('#changelist-filter > h2').click(function () {
        toggle_flag = ! toggle_flag;
        $('#changelist-filter').find('> ul, > h3').each(function(){
                $(this).toggle(toggle_flag);
        });
    });
  });
})(django.jQuery);

Upvotes: 2

fanlix
fanlix

Reputation: 1378

Thanks to @JJ's idea. I added toggles for the whole window, simpler than @abyx's implement.

  1. Toggle the whole filter by clicking "Filter" title
  2. Toggle each list by clicking list title

This is the js file content:

;(function($){ $(document).ready(function(){
    $('#changelist-filter > h3').each(function(){
        var $title = $(this);
        $title.click(function(){
            $title.next().slideToggle();
        }); 
    });   
    var toggle_flag = true;
    $('#changelist-filter > h2').click(function () {
        toggle_flag = ! toggle_flag;
        $('#changelist-filter > ul').each(function(){
                $(this).toggle(toggle_flag);
        }); 
    });   
  }); 
})(django.jQuery);

Upvotes: 5

abyx
abyx

Reputation: 72808

I changed Jj's answer to collapse the whole filter when clicking on the 'filter' title, adding it here for completeness, a gist is available here:

(function($){
ListFilterCollapsePrototype = {
    bindToggle: function(){
        var that = this;
        this.$filterTitle.click(function(){
            that.$filterContent.slideToggle();
            that.$list.toggleClass('filtered');
        });
    },
    init: function(filterEl) {
        this.$filterTitle = $(filterEl).children('h2');
        this.$filterContent = $(filterEl).children('h3, ul');
        $(this.$filterTitle).css('cursor', 'pointer');
        this.$list = $('#changelist');
        this.bindToggle();
    }
}
function ListFilterCollapse(filterEl) {
    this.init(filterEl);
}
ListFilterCollapse.prototype = ListFilterCollapsePrototype;

$(document).ready(function(){
    $('#changelist-filter').each(function(){
        var collapser = new ListFilterCollapse(this);
    });
});
})(django.jQuery);

Upvotes: 9

Jj.
Jj.

Reputation: 3160

Given that you now have jQuery in django admin, it's easy to bind a slideToggle() to the titles in the List Filter.

This seems enough Javascript for it to work:

// Fancier version https://gist.github.com/985283 

;(function($){ $(document).ready(function(){
    $('#changelist-filter').children('h3').each(function(){
        var $title = $(this);
        $title.click(function(){
            $title.next().slideToggle();
        });
    });   
  });
})(django.jQuery);

Then in the ModelAdmin subclass you want to activate that set the Media inner class:

class MyModelAdmin(admin.ModelAdmin):
  list_filter = ['bla', 'bleh']
  class Media:
    js = ['js/list_filter_collapse.js']

Make sure to drop the list_filter_collapse.js file in a 'js' folder inside your STATIC_DIRS or STATIC_ROOT (Depending on your Django version)

Upvotes: 14

Related Questions