问题
sorry if this question has been answered before, but I did a lot of googling without success.
I know how to create custom list_filter
s in admin views (e.g. subclassing SimpleFilter
).
What I really would like, is a way (on the admin list view) to "check" different filters that combines them in a OR formula.
As an example, suppose you have:
# models.py
class Foo(models.Model):
foobar = ...
foofie = ...
...
# admin.py
class FooAdmin(admin.ModelAdmin):
list_filter = ( "foobar", "foofie" )
...
In the admin list view generated by FooAdmin
I can choose to filter records either by foobar
or by foofie
. Is there a way to filter them by the formula: foobar = X OR foofie = Y
, where X
and Y
are two values that foobar
and foofie
can assume?
Is it even possible?
I know not everything is possible in the django admin views, but it seems a very common request, I wonder if I missed to understand or read something.
Also third party apps allowing it are welcome. Thanks :)
回答1:
I found a third party app just now, it is django-advanced-filters which may fit you requirement .
It has:
The OR field
OR is an additional field that is added to every rule's available fields.
It allows constructing queries with OR statements. You can use it by creating an "empty" rule with this field "between" a set of 1 or more rules.
I have run a test, add a OR field
would work.
This is the screenshot:
回答2:
Firstly I try to explain the working of django admin filters. When you want to filter your queryset in admin page django looks for all registered filters. If you set value for filter django filter queryset with this value. If you set more than one filter django filter your queryset twice, this equal to queryset = queryset.filter(param1=1).filter(param2=2) or in SQL: SELECT ... WHERE param1=1 AND param2=2. It because you can't do it with standard django's filters. But you can write your own filter like this:
from django.contrib.admin import SimpleListFilter
from django.db.models import Q
from functools import reduce
import operator
from django.core.exceptions import FieldError
class ORListFilter(SimpleListFilter):
title = ''
parameter_name = ''
search_field = ('',)
def queryset(self, request, queryset):
filters = request.GET.copy()
try: #for search
search_field_value = filters.pop('q')[0]
query_params = [Q((key, search_field_value)) for key in self.search_field]
try:
queryset = queryset.filter(reduce(operator.or_, query_params))
except FieldError:
pass
except KeyError:
pass
try:
query_params = [Q((key, value)) for key, value in filters.dict().items()]
queryset = queryset.filter(reduce(operator.or_, query_params))
except TypeError:
pass
return queryset
def lookups(self, request, model_admin):
qs = model_admin.get_queryset(request)
parameters = qs.all().values(self.parameter_name).distinct()
for parameter in parameters:
value = dict(parameter).pop(self.parameter_name, None)
if value:
yield (value, value)
else:
yield (None, 'NULL')
class Field1Filter(ORListFilter):
title = 'title'
parameter_name = 'field1'
search_field = ('search1', 'search2')
class Field2Filter(ORListFilter):
title = 'title'
parameter_name = 'field2'
search_field = ('search1', 'search2')
And register it in admin:
search_fields = ('search1', 'search2')
list_filter = (Field1Filter, Field2Filter)
It doesn't work with standard django's filters and all values in list_filter must inherited from ORListFilter class. Also it doesn't work with datetime filters, but you can add this ability.
回答3:
Figured out a 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
# see https://github.com/django/django/blob/1.8.5/django/contrib/admin/views/main.py#L104
def get_filters(self, request, params, prefix=''):
filter_specs = []
for field_path in self.fields:
field = get_fields_from_path(self.model, field_path)[-1]
field_list_filter_class = FieldListFilter.create
spec = field_list_filter_class(field, request, params,
self.model, self.model_admin, field_path=prefix + field_path)
# Check if we need to use distinct()
# use_distinct = (use_distinct or
# lookup_needs_distinct(self.lookup_opts,
# field_path))
filter_specs.append(spec)
return filter_specs
def expected_parameters(self):
parameters = []
for spec in self.filter_specs:
parameters += spec.expected_parameters()
return parameters
def choices(self, cl):
return []
def queryset(self, request, queryset):
origin_GET = request.GET.copy()
fake_GET = QueryDict(mutable=True)
fake_GET.update(self.used_parameters)
request.GET = fake_GET
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
# restore
request.GET = origin_GET
return queryset
class OrFilter(OrListFilter):
title = 'Or filter'
parameter_prefix = 'or1'
fields = ("foobar", "foofie")
class FooAdmin(admin.ModelAdmin):
list_filter = (OrFilter, )
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 %}
Borrowed some code from @dima-kudosh.
Explanation
ChangeList.get_filters() creates ListFilter
s (filter_specs) from ModelAdmin.list_filter
, then uses ListFilter.queryset() to get_queryset().
FieldListFilter.queryset() uses used_parameters
to filter queryset: queryset.filter(**self.used_parameters)
.
So we can create FieldListFilter
s from OrListFilter.fields
and use their used_parameters
to construct 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
回答4:
Django Admin Multiple Choice List Filter is a Django app that I wrote to fulfil this requirement, after searching through many posts like this one.
MultipleChoiceListFilter extends SimpleListFilter to allow you to filter on multiple options.
The UI uses clickable links to 'include' and 'exclude' choices from the 'OR' query, rather than ticking/unticking a checkbox. Thus, you have to wait for a round trip to the server, and for the page to refresh, after each click. This could be a performance/UX issue, especially for large numbers of objects.
The behaviour of the 'All' link, and of each choice link, is preserved from the SimpleListFilter - i.e. you can reset the filter to all, or just one, of the choices.
Currently included choices are highlighted in the filter (in blue in the screenshot below).
The template is overridable so you change the interface to suit your needs. Personally I think a bit more space between the choice name and the include/exclude link might help to differentiate the two. Or perhaps a switch icon would be more intuitive than the word 'include'/'exclude'.
来源:https://stackoverflow.com/questions/26869744/django-admin-list-filter-or-condition