代码之家  ›  专栏  ›  技术社区  ›  Antoine Pinsard

django过滤器与空字段混淆

  •  10
  • Antoine Pinsard  · 技术社区  · 6 年前

    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\uu in”为空时,querystring看起来像“?state\uu in=1”。在这种情况下,我的页面中没有结果(这是出乎意料的,如果没有填写某个字段,我希望结果不会在此字段上过滤)。

    我查看了debug工具栏以获得有关已执行SQL查询的更多信息。令人惊讶的是,我没有找到相关查询集的SQL查询(如果querystring为“”,则state\uu in=1&例如,结果与预期一致,我可以在调试工具栏中找到相关查询)

    所以我尝试使用 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: 
    

    现在我卡住了,我不知道出了什么问题,以及如何进一步调试。

    另外,这里是相关的mixin 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
    

    最后 基本列表视图 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 回复  |  直到 6 年前
        1
  •  7
  •   Kamil Niski    6 年前

    我认为文档回答了你的问题:

    Filtering by an empty string

    值被解释为跳过的筛选器。

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

    在文档中,您还可以看到可能的解决方案示例。我把其中一个放在这里

    解决方案1:神奇的价值观

    可以重写filter类的filter()方法 检查魔法值。这类似于ChoiceFilters null 价值处理。

    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):
    

        2
  •  3
  •   Antoine Pinsard    6 年前

    我终于弄明白了问题所在。

    django-filters in 外键。的默认筛选器 source__in 例如,是 ModelChoiceFilter ModelMultipleChoiceFilter .

    source__in=10&source__in=7 大致翻译成 Q(source__in=10) | Q(source__in=7) . 这是一个例外,因为10和7是不可数的。所以我把代码改成了 exact 查找而不是 在里面 模型多重回声滤波器 . 最后,得出以下结论:

    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