It turned out the solution:
import operator from functools import reduce from django.contrib.admin import ListFilter, FieldListFilter from django.db.models import Q from django.contrib.admin.utils import ( get_fields_from_path, lookup_needs_distinct, prepare_lookup_value, ) from django.http import QueryDict class OrListFilter(ListFilter): parameter_prefix = None fields = None def __init__(self, request, params, model, model_admin): super(OrListFilter, self).__init__( request, params, model, model_admin) if self.parameter_prefix is None: raise ImproperlyConfigured( "The list filter '%s' does not specify " "a 'parameter_prefix'." % self.__class__.__name__) self.model_admin = model_admin self.model = model self.request = request self.filter_specs = self.get_filters(request, {}, prefix=self.parameter_prefix+'-') for p in self.expected_parameters(): if p in params: value = params.pop(p) field = p.split('-')[1] self.used_parameters[field] = prepare_lookup_value(field, value) def has_output(self): return True
APP_NAME / templates / admin / app_name / change_list.html:
{% extends "admin/change_list.html" %} {% load i18n admin_list %} {% block filters %} {% if cl.has_filters %} <div id="changelist-filter"> <h2>{% trans 'Filter' %}</h2> {% for spec in cl.filter_specs %} {% if spec.filter_specs %} {% admin_list_filter cl spec %} <ul> {% for sub_spec in spec.filter_specs %} <li>{% admin_list_filter cl sub_spec %}</li> {% endfor %} </ul> {% else %} {% admin_list_filter cl spec %} {% endif %} {% endfor %} </div> {% endif %} {% endblock %}
I borrowed some code from @ dima-kudosh.
Description
ChangeList.get_filters() creates ListFilter (filter_specs) from ModelAdmin.list_filter , then uses ListFilter.queryset() - get_queryset() .
FieldListFilter.queryset() uses used_parameters to filter the request: queryset.filter(**self.used_parameters) .
So, we can create a FieldListFilter from OrListFilter.fields and use their used_parameters to create OR queries:
all_params = {} for spec in self.get_filters(request, self.used_parameters): if spec and spec.has_output(): all_params.update(spec.used_parameters) try: query_params = [Q((key, value)) for key, value in all_params.items()] queryset = queryset.filter(reduce(operator.or_, query_params)) except TypeError as e: pass