фильтр списка администраторов 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, но это кажется очень распространенным запросом, я задаюсь вопросом, не пропустил ли я что-то понять или прочитать.



Также приветствуются сторонние приложения, позволяющие это. Спасибо :)

602   3  

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

    Ничего не найдено.