Created
February 7, 2024 11:16
-
-
Save jurrian/7ce23f918f8d06f7b9d5a3dcebb3db38 to your computer and use it in GitHub Desktop.
FilterColumnsMixin for adding a button for selecting which columns to show in Django admin changelist
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from app.forms import FilterColumnsForm | |
from django.contrib.admin.utils import label_for_field | |
from django.template.response import TemplateResponse | |
from django.urls import path | |
from django.utils.translation import gettext_lazy as _ | |
class FilterColumnsMixin: | |
"""Allow users to filter the columns in the changelist. | |
This will add a "Columns" button to the changelist page to select the columns to display. | |
""" | |
filter_columns_flag = True # Just a flag to see in template if mixin is present | |
def get_list_display(self, request): | |
"""Filter the list_display based on the form response. | |
If session middleware is active, store it per model on current user session. | |
When for some reason session is disabled then save it on the class instance itself, | |
will be the same for all users and will reset on application restart. | |
""" | |
model_name = self.model._meta.model_name # pylint: disable=protected-access | |
filter_columns = request.GET.getlist('filter_columns') | |
is_reset = request.GET.get('reset_columns') | |
default_list_display = super().get_list_display(request) | |
if hasattr(request, 'session'): | |
# In most cases session middleware is used | |
if is_reset: | |
try: | |
del request.session['filter_columns'][model_name] | |
request.session.modified = True | |
except KeyError: | |
pass | |
# If all columns are selected, don't store it in session | |
elif filter_columns and filter_columns != list(default_list_display): | |
request.session['filter_columns'] = {model_name: filter_columns} | |
try: | |
return request.session['filter_columns'][model_name] | |
except (KeyError, TypeError): | |
return default_list_display | |
else: | |
# Fall back to "persisting" on the class instance list_display attribute | |
if is_reset: | |
self.list_display = default_list_display | |
elif filter_columns: | |
self.list_display = filter_columns | |
return self.list_display | |
def get_list_display_links(self, request, list_display): | |
"""When display link is not in list_display, use the first column as fallback. | |
Otherwise, there will be no link to click on. | |
""" | |
result = super().get_list_display_links(request, list_display) | |
list_display = self.get_list_display(request) | |
if list_display and not [x for x in result if x in list_display]: | |
result = list_display[0] | |
return result | |
def get_urls(self): | |
"""Adds a intermediary columns page to this admin. | |
""" | |
name = f'{self.model._meta.app_label}_{self.model._meta.model_name}_columns' # pylint: disable=protected-access | |
urls = super().get_urls() | |
return [ | |
path('columns/', self.admin_site.admin_view(self.filter_columns), name=name), | |
] + urls | |
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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% extends 'admin/change_list.html' %} | |
{% load i18n admin_urls %} | |
{% block object-tools-items %} | |
{% if cl.pk_attname != 'history_id' %} | |
{% url opts|admin_urlname:'changelist_history' as history_url %} | |
{% if history_url %} | |
<li> | |
<a href="{% add_preserved_filters history_url %}" class="historylink">{% translate "History" %}</a> | |
</li> | |
{% endif %} | |
{% endif %} | |
{% if cl.model_admin.filter_columns_flag %} | |
<li><a href="{% url opts|admin_urlname:'columns' %}">Columns</a></li> | |
{% endif %} | |
{{ block.super }} | |
{% endblock %} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% extends 'admin/base_site.html' %} | |
{% load admin_urls static %} | |
{% block extrahead %} | |
{{ block.super }} | |
<link rel="stylesheet" href="{% static "admin/css/forms.css" %}"> | |
{{ form.media }} | |
{% endblock %} | |
{% block content %} | |
<form action="{% url opts|admin_urlname:'changelist' %}" method="GET"> | |
{{ form.as_div }} | |
<div class="submit-row" style="clear: both"> | |
<input type="submit" value="OK" class="default"> | |
<input type="submit" name="reset_columns" value="Reset"> | |
</div> | |
</form> | |
{% endblock %} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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',) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment