Filter by many2many (or foreignkey) that has a LOT of values, in django admin (grappelli or not)

753 Views Asked by At

I have two models:

class TheMainThing(models.Model):
    ... some fields ...
    type = ManyToManyField('Type')

class Type(models.Model):
    ...more fields...

I would like to enable filtering TheMainThings in the admin by Types. The only Issue is that there are like 100k or more of the Type objects, so it isn't practical doing it with standard built-in filters. That's because all the Types get loaded in browser as an available option. (Actually, I'm using Grappelli, but I believe it's the same with standard Django admin).

It there a *don't_reinvent_the_wheel* approach to this problem?

I like Grappelli's autocomplete widget on raw_id fields, I imagine something like that would be ideal for this problem... It there such a thing?

edit:
To clarify - the main problem is load time and memory consumption in browser, not the presentation.

1

There are 1 best solutions below

4
On

The admin outputs those filters as links with query strings inside <ul>s. You could change those out for selects instead.

The template used there is admin/filter.html and it looks like this:

{% load i18n %}
<h3>{% blocktrans with filter_title=title %} By {{ filter_title }} {% endblocktrans %}</h3>
<ul>
{% for choice in choices %}
    <li{% if choice.selected %} class="selected"{% endif %}>
    <a href="{{ choice.query_string|iriencode }}">{{ choice.display }}</a></li>
{% endfor %}
</ul>

You can provide your own by the same name to override theirs, so redo it maybe like this:

{% load i18n %}
<h3>{% blocktrans with filter_title=title %} By {{ filter_title }} {% endblocktrans %}</h3>
<select>
{% for choice in choices %}
    <option data-href="{{ choice.query_string|iriencode }}"
            {% if choice.selected %} selected="selected"{% endif %}>
    {{ choice.display }}
    </option>
{% endfor %}
</select>

Then follow the filter link on change:

$(function() {
    $('select', '#changelist-filter').on('change', function(e) {
        window.location = window.location.href + $(e.target).attr('data-href');
    });

});

To take it a step further, you could treat those with something like Select2 afterward.