いくつかのリストをフィルタリングするように Django-filter を設定しました。カスタムフォームを使用したものの1つを次に示します。
_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クエリが見つかりませんでした。 (たとえば、querystringが "?state__in = 1&source__in = 2"の場合、結果は期待どおりであり、デバッグツールバーで関連するクエリを見つけることができます)
そこで、print(str(filters.qs.query))
を使用してSQLクエリの印象を強制しようとしました。新しい驚き、これは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
_です。
Kamilが提案したように、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
_のバグですか、それとも何か間違ったことをしていますか?
私はついに問題を理解しました。
どうやら_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
_
ドキュメントがあなたの質問に答えると思います:
空の値はスキップされたフィルタとして解釈されるため、現在、空の文字列でフィルタリングすることはできません。
_GET http://localhost/api/my-model?myfield=
_
さらにドキュメントには、考えられる解決策の例があります。私はここにそれらの1つを置いています
解決策1:マジック値
フィルタクラスのfilter()メソッドをオーバーライドして、マジック値を具体的にチェックできます。これは、ChoiceFilterの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
_
今、私はあなたの問題を解決するのに十分な情報がないと感じています。あなたの質問の下にコメントを残しました。その追加情報を提供できれば、何が起こっているのかを理解するのに大いに役立ちます。
このバグを追跡するのに役立つヒントをいくつか紹介します。
行の前にブレークポイント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()
_
あなたはこのようなことをすることができます
_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がアプリを停止し、コードにステップインして検査できるようになります。