django-фильтр возится с пустым полем - PullRequest
0 голосов
/ 30 сентября 2018

Я установил django-filter , чтобы отфильтровать некоторые из моих списков.Вот один из них, с пользовательской формой:

class BookingListFiltersForm(forms.Form):

    state__in = forms.MultipleChoiceField(
        choices=Booking.STATE_CHOICES, required=False,
        label=_("État"), widget=forms.CheckboxSelectMultiple)
    source__in = forms.ModelMultipleChoiceField(
        queryset=Platform.objects.all(), required=False,
        label=_("Source"), widget=ModelSelect2Multiple(
            url='autocomplete:platform'))


class BookingManagerFilter(filters.FilterSet):

    payments__date = filters.DateFilter(method='payments__date_filter')
    payments__method = filters.ChoiceFilter(
        method='payments__method_filter',
        choices=BookingPayment.METHOD_CHOICES,
    )

    class Meta:
        model = Booking
        fields = {
            'period': [
                'endswith', 'endswith__gte', 'endswith__lte',
                'startswith', 'startswith__gte', 'startswith__lte',
            ],
            'state': ['in'],
            'source': ['in'],
            'booking_date': ['date', 'date__lte', 'date__gte'],
            'accommodation': ['in'],
            'guest': ['exact']
        }

    def get_form_class(self):
        return BookingListFiltersForm

    def payments__date_filter(self, queryset, name, value):
        return queryset.filter(**{name: value})

    def payments__method_filter(self, queryset, name, value):
        return queryset.filter(**{name: value})

Форма отправляется методом GET.Когда поле «source__in» пусто, строка запроса выглядит так: «? State__in = 1».В этом случае у меня нет результата на моей странице (что является неожиданным, если поле не заполнено, я ожидаю, что результаты не фильтруются по этому полю).

Я посмотрел на панель инструментов отладки, чтобыиметь больше информации о выполненном запросе SQL.Удивительно, но я не нашел SQL-запрос для соответствующего набора запросов!(хотя, например, строка запроса "? state__in = 1 & source__in = 2", результат такой же, как и ожидалось, и я могу найти связанные запросы на панели инструментов отладки)

Поэтому я попытался создать впечатление запроса SQL, используяprint(str(filters.qs.query)).Новый сюрприз, это вызвало исключение EmptyResultSet:

Traceback:

File "/home/tony/.venvs/cocoonr/lib/python3.6/site-packages/django/core/handlers/exception.py" in inner
  35.             response = get_response(request)

File "/home/tony/.venvs/cocoonr/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
  128.                 response = self.process_exception_by_middleware(e, request)

File "/home/tony/.venvs/cocoonr/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
  126.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/home/tony/.venvs/cocoonr/lib/python3.6/site-packages/django/views/generic/base.py" in view
  69.             return self.dispatch(request, *args, **kwargs)

File "/home/tony/.venvs/cocoonr/lib/python3.6/site-packages/django/utils/decorators.py" in _wrapper
  62.             return bound_func(*args, **kwargs)

File "/home/tony/.venvs/cocoonr/lib/python3.6/site-packages/django/contrib/auth/decorators.py" in _wrapped_view
  21.                 return view_func(request, *args, **kwargs)

File "/home/tony/.venvs/cocoonr/lib/python3.6/site-packages/django/utils/decorators.py" in bound_func
  58.                 return func.__get__(self, type(self))(*args2, **kwargs2)

File "/home/tony/Workspace/cocoonr/utils/views/manager.py" in dispatch
  29.         return super().dispatch(*args, **kwargs)

File "/home/tony/.venvs/cocoonr/lib/python3.6/site-packages/django/views/generic/base.py" in dispatch
  89.         return handler(request, *args, **kwargs)

File "/home/tony/.venvs/cocoonr/lib/python3.6/site-packages/django/views/generic/list.py" in get
  142.         self.object_list = self.get_queryset()

File "/home/tony/Workspace/cocoonr/booking/views/manager.py" in get_queryset
  73.         queryset = super().get_queryset()

File "/home/tony/Workspace/cocoonr/utils/views/common.py" in get_queryset
  118.         print(self.filters.qs.query)

File "/home/tony/.venvs/cocoonr/lib/python3.6/site-packages/django/db/models/sql/query.py" in __str__
  252.         sql, params = self.sql_with_params()

File "/home/tony/.venvs/cocoonr/lib/python3.6/site-packages/django/db/models/sql/query.py" in sql_with_params
  260.         return self.get_compiler(DEFAULT_DB_ALIAS).as_sql()

File "/home/tony/.venvs/cocoonr/lib/python3.6/site-packages/django/db/models/sql/compiler.py" in as_sql
  461.                 where, w_params = self.compile(self.where) if self.where is not None else ("", [])

File "/home/tony/.venvs/cocoonr/lib/python3.6/site-packages/django/db/models/sql/compiler.py" in compile
  393.             sql, params = node.as_sql(self, self.connection)

File "/home/tony/.venvs/cocoonr/lib/python3.6/site-packages/django/db/models/sql/where.py" in as_sql
  98.                     raise EmptyResultSet

Exception Type: EmptyResultSet at /manager/booking/bookings/
Exception Value: 

Теперь я застрял, я понятия не имею, что идет не так и как отлаживать дальше.

Я пытался пройтиследующая строка запроса для тестирования: "? state__in = 1 & source__in =".В этом случае фильтрация работает должным образом, но в форме фильтра отображается ошибка «« »не является допустимым значением для поля« source__in ».

Кроме того, здесь приведен соответствующий миксин в utils/views/common.py:

class ListFilterMixin:

    filters_class = None
    default_filters = None

    @cached_property
    def filters(self):
        return self.get_filters()

    def get_filters(self):
        if self.filters_class:
            qstring = self.request.GET
            if not qstring and self.default_filters:
                qstring = QueryDict(self.default_filters)
            return self.filters_class(
                qstring, self.get_unfiltered_queryset(), request=self.request)
        else:
            return None

    def get_queryset(self):
        print(self.filters.qs.query)  # <--- Line 118
        # ...

    def get_unfiltered_queryset(self):
        return super().get_queryset()

И класс представления в booking/views/manager.py:

class BookingListView(ListView):
    """List of all bookings."""

    model = Booking
    default_filters = 'state__in=1'
    filters_class = BookingManagerFilter
    paginate_by = 30
    ordering = '-pk'

    def get_queryset(self):
        queryset = super().get_queryset()  # <--- Line 73
        # ...

Кроме того, чтобы у вас было полное дерево наследования, обратите внимание, что ListView, использованное выше, равно utils.views.manager.ListView:

class ListView(BulkActionsMixin, ManagerMixin, BaseListView):
    pass

И BaseListView равно utils.views.common.ListView:

class ListView(ListFilterMixin, AgencyMixin, ContextMixin, BaseListView):
    pass

Последний BaseListView равен django.views.generic.list.ListView.


Использование ipdb для отладки в соответствии с предложением Камиля я заметил странную вещь, которая, вероятно, является причиной такого поведения:

ipdb> next
> /home.tony/.venvs/cocoonr/lib/python3.6/site-packages/django_filters/filters.py(167)filter()
    166     def filter(self, qs, value):
--> 167         if value != self.null_value:
    168             return super().filter(qs, value)

ipdb> self.null_value
'null'
ipdb> value
<QuerySet []>
ipdb> self.field_name
'source'
ipdb> self.lookup_expr
'in'
ipdb> 

Таким образом, следующий код, рассматривающий source__in, не является пустым и добавляет source__in=empty_queryset кфильтры.Я предполагаю, что django затем полагает, что результат не может быть оценен как непустой набор запросов и сохраняет бесполезный запрос.

Это ошибка в django-filters или я что-то делаю неправильно?

Ответы [ 2 ]

0 голосов
/ 15 октября 2018

Я наконец выяснил проблему.

Очевидно, django-filters неправильно обрабатывает поиск in для внешних ключей.Например, фильтр по умолчанию для source__in - ModelChoiceFilter.Поэтому мне пришлось явно определить его как ModelMultipleChoiceFilter.

Однако я столкнулся с другой проблемой, которая заключается в том, что source__in=10&source__in=7 примерно переводится в Q(source__in=10) | Q(source__in=7).Что вызывает исключение, так как 10 и 7 не являются повторяемыми.Поэтому я изменил свой код, чтобы использовать поиск exact вместо in, но все еще использовал ModelMultipleChoiceFilter.Что, в конце концов, дает следующее:

class BookingListFiltersForm(forms.Form):

    state__in = forms.MultipleChoiceField(
        choices=Booking.STATE_CHOICES, required=False,
        label=_("État"), widget=forms.CheckboxSelectMultiple)
    source = forms.ModelMultipleChoiceField(
        queryset=Platform.objects.all(), required=False,
        label=_("Source"), widget=ModelSelect2Multiple(
            url='autocomplete:platform'))


class BookingManagerFilter(filters.FilterSet):

    source = filters.ModelMultipleChoiceFilter(
        queryset=Platform.objects.all())
    payments__date = filters.DateFilter(method='payments__date_filter')
    payments__method = filters.ChoiceFilter(
        method='payments__method_filter',
        choices=BookingPayment.METHOD_CHOICES,
    )

    class Meta:
        model = Booking
        fields = {
            'period': [
                'endswith', 'endswith__gte', 'endswith__lte',
                'startswith', 'startswith__gte', 'startswith__lte',
            ],
            'state': ['in'],
            'source': ['exact'],
            'booking_date': ['date', 'date__lte', 'date__gte'],
            'accommodation': ['exact'],
            'guest': ['exact']
        }

    def get_form_class(self):
        return BookingListFiltersForm
0 голосов
/ 03 октября 2018

Я думаю, что документация отвечает на ваш вопрос:

Фильтрация по пустой строке

В настоящее время невозможно фильтровать по пустой строке, так как пустые значенияинтерпретируются как пропущенный фильтр.

GET http://localhost/api/my-model?myfield=

Далее в документах приведены примеры возможных решений.Я помещаю сюда один из них

Решение 1: Магические значения

Вы можете переопределить метод filter () класса фильтра, чтобы специально проверять магические значения,Это похоже на обработку пустых значений в ChoiceFilter.

GET http://localhost/api/my-model?myfield=EMPTY

class MyCharFilter(filters.CharFilter):
    empty_value = 'EMPTY'

    def filter(self, qs, value):
        if value != self.empty_value:
            return super(MyCharFilter, self).filter(qs, value)

        qs = self.get_method(qs)(**{'%s__%s' % (self.name, self.lookup_expr): ""})
        return qs.distinct() if self.distinct else qs

Сейчас я чувствую, что для решения вашей проблемы недостаточно информации.Я оставил комментарий под вашим вопросом.Если вы предоставите эту дополнительную информацию, это очень поможет понять, что происходит.

Вот несколько советов, которые могут помочь вам отследить эту ошибку:

  • Установка ipdb.это поможет вам выполнить код шаг за шагом и проверить каждую переменную.
  • Удалить точку останова import ipdb;ipdb.set_trace() перед строкой

    File "/home/tony/.venvs/cocoonr/lib/python3.6/site-packages/django/views/generic/list.py" in get
      142.         self.object_list = self.get_queryset()
    

Я подозреваю, вы должны найтивиновник в https://github.com/carltongibson/django-filter/blob/82a47fb7bbddedf179f110723003f3b28682d7fe/django_filters/filterset.py#L215

Вы можете сделать что-то вроде этого

class BookingManagerFilter(filters.FilterSet):
    # your previous code here

    def filter_queryset(self, queryset):
        import ipdb;ipdb.set_trace()
        return super(BookingManagerFilter, self)filter_queryset(queryset):

И запустить свою конечную точку, ipdb остановит приложение, и вы сможете войти в код и проверить его.

...