Selection widget for which columns to display in Django admin

When working in the Django admin with a lot of users, you might sometimes want to offer the functionality to users to choose which columns they want to see in the changelist page. For some users some columns might be more relevant than to others.
One way to achieve this is by putting a button in the top right corner that will lead you to a intermediary page like this:

Using the FilteredSelectMultiple widget
For this you need a special django form, that will show the so called FilteredSelectMultiple widget that is shown above. This widget takes quite some effort to get working properly, as normally this only works out-of-the-box by using filter_horizontal = [...] or filter_vertical = [...] in a ModelAdmin.
The trick here is to add the class Media which will ensure that the correct javascript is loaded to make this widget work. As you see in the Gist below in filter_columns.html , you also need to include a stylesheet for the form.css or it will not look as nice.
from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
class FilterColumnsForm(forms.Form):
"""Form with only one field to filter the columns.
Django widget for horizontal selection, works only in the admin and only with media included.
"""
filter_columns = forms.MultipleChoiceField(
label=False,
widget=FilteredSelectMultiple('columns', is_stacked=False)
)
class Media:
js = ('/admin/jsi18n',)
This setup allows you to use FilteredSelectMultiple basically everywhere outside the regular admin configuration for if you want to customize the use or options.
ModelAdmin mixin to create an intermediary page
In get_urls() you want to append a new path that for the intermediary page for when you click on the "Columns" button. Based on the list_display parameter you can create some
def filter_columns(self, request):
"""View for intermediary page when clicking on the "Columns" button.
Will render an admin view with standard context, showing the `FilterColumnsForm`.
"""
cl = self.get_changelist_instance(request)
# Determine which to display in the form and which are already selected
display = []
selected = []
all_fields = type(self).list_display
for field_name in all_fields:
text = label_for_field(field_name, cl.model, model_admin=cl.model_admin, return_attr=False)
display.append((field_name, text))
if field_name in cl.list_display:
selected.append(field_name)
# Initialize form with selected and set the available choices
form = FilterColumnsForm(initial={'filter_columns': selected})
form['filter_columns'].field.choices = display
# Add context for showing the menu and bars
context = self.admin_site.each_context(request)
context['title'] = _('Choose which columns to show')
context['form'] = form
context['opts'] = self.model._meta # pylint: disable=protected-access
request.current_app = self.admin_site.name
return TemplateResponse(request, 'admin/filter_columns.html', context)
The whole code can be found here on Github:
Use this structure as it matters where the templates are placed, replace app by your own name:
app/admin.py
app/forms.py
app/templates/admin/filter_columns.html
app/templates/admin/change_list.html