фильтр списка администраторов django " или " условие
Извините, если на этот вопрос был дан ответ раньше, но я много гуглил без успеха.
Я знаю, как создавать пользовательские list_filter s в представлениях администратора (например, подклассы SimpleFilter).
Что мне действительно хотелось бы, так это способ (в представлении списка администраторов) "проверять" различные фильтры, объединяя их в Формулу OR.
В качестве примера предположим, что у вас есть:
# models.py
class Foo(models.Model):
foobar = ...
foofie = ...
...
# admin.py
class FooAdmin(admin.ModelAdmin):
list_filter = ( "foobar", "foofie" )
...
В представлении списка администраторов, созданном FooAdmin , я могу выбрать фильтрацию записей либо по foobar, либо по foofie. Есть ли способ фильтровать их по формуле: foobar = X OR foofie = Y, где X и Y - два значения, которые foobar и foofie могут принимать?
Возможно ли это вообще?
Я знаю, что не все возможно в представлениях администратора django, но это кажется очень распространенным запросом, я задаюсь вопросом, не пропустил ли я что-то понять или прочитать.
Также приветствуются сторонние приложения, позволяющие это. Спасибо :)
3 ответов:
Я только что нашел стороннее приложение, это django-advanced-filters, которое может соответствовать вашим требованиям .
Он имеет:
Поле или
Или это дополнительное поле, которое добавляется к каждому доступному правилу поля.
Он позволяет строить запросы с помощью операторов OR. Вы можете использовать его путем создание" пустого "правила с этим полем "между" набором из 1 или более правила.
Я запустил тест, добавить
OR fieldбудет работать. Это скриншот:
Во-первых, я пытаюсь объяснить работу фильтров администратора django. Если вы хотите, чтобы отфильтровать объект QuerySet в админку Джанго смотрит на всех зарегистрированных фильтров. Если вы установите значение для фильтра класса QuerySet в Django с этим значением. Если вы устанавливаете больше чем один фильтр Джанго ваш объект QuerySet в два раза, это равный объект QuerySet = объект QuerySet.фильтр (param1=1).filter (param2=2 )или в SQL: SELECT... Где param1=1 и param2=2. Это потому, что вы не можете сделать это с помощью стандартных фильтров django. Но вы можете написать свой собственный фильтр, такой как этот:
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')И зарегистрируйте его в admin:
search_fields = ('search1', 'search2') list_filter = (Field1Filter, Field2Filter)Он не работает со стандартными фильтрами django, и все значения в list_filter должны быть унаследованы от класса ORListFilter. Также он не работает с фильтрами datetime, но вы можете добавить эту возможность.
Нашел решение:
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/шаблоны/администратора/функция 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 %}Позаимствовал код у @dima-kudosh.
Объяснение
ChangeList.get_filters()создаетListFilters (filter_specs) изModelAdmin.list_filter, затем используетListFilter.queryset()чтобыget_queryset().Таким образом, мы можем создать
FieldListFilter.queryset()используетused_parametersдля фильтрации queryset:queryset.filter(**self.used_parameters).FieldListFilters изOrListFilter.fieldsи использовать ихused_parametersдля построения или запросы: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

Comments