Reputation: 170320
If, for a field that you want to filter by, you have more than ~10 values, the filtering sidebar starts to be ugly and harder to use.
I'm looking for a solution to replace the <li>
with a dropdown selection (combobox) or something similar that will solve the same problem.
Upvotes: 60
Views: 46578
Reputation: 1614
To add search functionality to @beholderrk and @Gediminas solution use the follow
Create searchable_dropdown_filter.html
in templates/admin
folder and paste the following code
{% load i18n %}
{% load static %}
<!-- Django's Select2 CSS -->
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/vendor/select2/select2.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/autocomplete.css' %}">
<!-- jQuery (Django Admin uses this, so you can refer to it) -->
<script src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script>
<!-- Django's Select2 JS -->
<script src="{% static 'admin/js/vendor/select2/select2.full.js' %}"></script>
<script type="text/javascript">
$(document).ready(function() {
$('.searchable-select').select2();
});
var go_from_select = function(opt) { window.location = window.location.pathname + opt };
</script>
<h3>{{ title }}</h3>
<ul class="admin-filter-{{ title|cut:' ' }}">
{% if choices|slice:"4:" %}
<li>
<select style="width: 95%;"
onchange="go_from_select(this.options[this.selectedIndex].value)"
class="searchable-select"
>
{% for choice in choices %}
<option{% if choice.selected %} selected="selected"{% endif %}
value="{{ choice.query_string|iriencode }}">{{ choice.display }}</option>
{% endfor %}
</select>
</li>
{% else %}
{% for choice in choices %}
<li{% if choice.selected %} class="selected"{% endif %}>
<a href="{{ choice.query_string|iriencode }}">{{ choice.display }}</a></li>
{% endfor %}
{% endif %}
</ul>
In your admin.py
or filters.py
paste the following
from django.contrib.admin.filters import RelatedFieldListFilter
class SearchableDropdownFilter(RelatedFieldListFilter):
template = 'admin/searchable_dropdown_filter.html'
In your admin implementation use it as follows
class SomeAdmin(admin.ModelAdmin):
# ...
list_filter = (('country', SearchableDropdownFilter), ('city', SearchableDropdownFilter),)
This uses select2 to add search functionality to the dropdown. This is very handy when you have a ton of options you want to filter from.
Upvotes: 0
Reputation: 121
I was struggling with the same problem some few weeks back. So this answer might be useful to some developers from the future.
I managed to solve the problem by writing a custom template.html
I have bundled the code in an amazing package now that does the same for you, here's the link.
1. Installation:
pip install django-admin-searchable-dropdown
This command will install the latest version of the package in your project.
Now, include the package in your project by adding admin_searchable_dropdown
to your INSTALLED_APPS
inside settings.py
file.
2. Usage:
Let's say you have following models:
from django.db import models
class CarCompany(models.Model):
name = models.CharField(max_length=128)
class CarModel(models.Model):
name = models.CharField(max_length=64)
company = models.ForeignKey(CarCompany, on_delete=models.CASCADE)
And you would like to filter results in CarModelAdmin
on the basis of company
. You need to define search_fields
in CarCompany
and then define filter like this:
from django.contrib import admin
from admin_searchable_dropdown.filters import AutocompleteFilter
class CarCompanyFilter(AutocompleteFilter):
title = 'Company' # display title
field_name = 'company' # name of the foreign key field
class CarCompanyAdmin(admin.ModelAdmin):
search_fields = ['name'] # this is required for django's autocomplete functionality
# ...
class CarModelAdmin(admin.ModelAdmin):
list_filter = [CarCompanyFilter]
# ...
After following these steps you may see the filter as:
auto_complete
functionailty), so as long as the Django version you are using is greater than 2.0, you should be fine.list_filters
you may have, like change the Title above the dropdown, or a custom Search logic etc.str(obj)
Upvotes: 0
Reputation: 760
I am not a fan of all solutions provided up to now.
Why? If, for a field that you want to filter by, you have more than 10 values, a listview box isn't that handy, too. I advice to use the standard search field capability of django admin which will show you a search field:
class BooksAdmin(admin.ModelAdmin):
list_display = ('ISBN', 'title')
search_fields = ('ISBN',)
# instead of: list_filter = ('ISBN',)
ordering = ('title',)
Upvotes: 2
Reputation: 18925
Thanks @beholderrk, @gediminas and @jk-laiho! I packaged this into a reusable app.
Install:
pip install django-admin-list-filter-dropdown
Enable in settings.py
:
INSTALLED_APPS = (
...
'django_admin_listfilter_dropdown',
...
)
Use in admin.py
:
from django_admin_listfilter_dropdown.filters import (
DropdownFilter, ChoiceDropdownFilter, RelatedDropdownFilter
)
class EntityAdmin(admin.ModelAdmin):
...
list_filter = (
# for ordinary fields
('a_charfield', DropdownFilter),
# for choice fields
('a_choicefield', ChoiceDropdownFilter),
# for related fields
('a_foreignkey_field', RelatedDropdownFilter),
)
Here's what it looks like:
Upvotes: 67
Reputation: 409
Could you please give a complete example. it shows like before. here is my code
from django.contrib import admin
from pages.models import Post, Device, DeviceType, DeviceModel, Ipaddress, DeviceGroup, Location,Department,Comment
from django_admin_listfilter_dropdown.filters import DropdownFilter, RelatedDropdownFilter
class CommentInline(admin.TabularInline):
model = Comment
class IpaddressAdmin(admin.ModelAdmin):
prepopulated_fields = {'slug': ('ipaddress',)}
# model=Ipaddress
search_fields = ['ipaddress', ]
#
list_display = ('ipaddress', 'machinename', 'user', 'department','location',)
list_filter = (
('user', DropdownFilter),
('department', RelatedDropdownFilter),
('location', RelatedDropdownFilter),
)
Upvotes: 0
Reputation: 2106
The best solution is to create a new template in admin/filter.html
and implement the HTML code suggested by @beholderrk. Just implemented it for a client and it works great.
Problem with DropdownFilter and RelatedDropdownFilter
is that it loses the proper display. Instead of the translated strings for Charfield(choices=xxx)
, it will show True
, False
and so on.
Upvotes: 0
Reputation: 940
You can copy the admin templates from the django installation into you templates/admin folder in your project.
Then you will need to do any of 2 things in the forms or templates you want to show your outputs in:
If you are working with a form, in that you would like the list choices to be posted back to a database, you would in your model.py, on the field you have your choices, put in some this like this:
choice = forms.IntegerField(widget=forms.Select(choices=CHOICES))
If it is just to display on a page, then you will output on a template tag something like this:
<select>
{% for choices in object.details.all %}
<option> {{ object.choice }} </option>
{% endfor %}
</select>
Upvotes: 3
Reputation: 50776
An easy option would be to use django-grappelli, which replaces all the filters with drop downs.
Upvotes: 4
Reputation: 685
I cannot comment answers so I'll add to beholderrk's answer here.
dropdown_filter.html
or similardropdown_filter.html
create a new filter class in filters.py
:
from django.contrib.admin.filters import AllValuesFieldListFilter
class DropdownFilter(AllValuesFieldListFilter):
template = 'admin/dropdown_filter.html'
now you can use this filter in your admin class:
class SomeAdmin(admin.ModelAdmin):
# ...
list_filter = (('country', DropdownFilter),)
Works great!
Upvotes: 43
Reputation: 750
Use filter.html from feincms
{% load i18n %}
<script type="text/javascript">var go_from_select = function(opt) { window.location = window.location.pathname + opt };</script>
<h3>{{ title }}</h3>
<ul class="admin-filter-{{ title|cut:' ' }}">
{% if choices|slice:"4:" %}
<li>
<select style="width: 95%;"
onchange="go_from_select(this.options[this.selectedIndex].value)">
{% for choice in choices %}
<option{% if choice.selected %} selected="selected"{% endif %}
value="{{ choice.query_string|iriencode }}">{{ choice.display }}</option>
{% endfor %}
</select>
</li>
{% else %}
{% for choice in choices %}
<li{% if choice.selected %} class="selected"{% endif %}>
<a href="{{ choice.query_string|iriencode }}">{{ choice.display }}</a></li>
{% endfor %}
{% endif %}
</ul>
Upvotes: 34