-
Notifications
You must be signed in to change notification settings - Fork 770
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
FilterMethod.__call__ calling method
with value=<Queryset []>
#1550
Comments
Further study about Django forms: Setupfrom django import forms
from django.contrib.auth import get_user_model
User = get_user_model()
class MyForm(forms.Form):
users = forms.ModelMultipleChoiceField(queryset=User.objects.all(), required=False) Empty formform = MyForm(data={"users":[]})
form.is_valid()
print(form.cleaned_data) # {'users': <Queryset []>} Non-empty formform = MyForm(data={"users":[1]})
form.is_valid()
print(form.cleaned_data) # {'users': <Queryset [<User: root@root.com>]>} It appears that vanilla Django forms return Queryset instances for the ModelMultipleChoiceField. So that leaves the question of why does The metaclass Setup, but with django-filter fieldsfrom django import forms
from django.contrib.auth import get_user_model
from django_filters import fields as django_filter_fields
User = get_user_model()
class MyForm(forms.Form):
users = django_filter_fields.ModelMultipleChoiceField(queryset=User.objects.all(), required=False) Empty formform = MyForm(data={"users":[]})
form.is_valid()
print(form.validated_data) # dict_values([<QuerySet []>]) Non-empty formform = MyForm(data={"users":[1]})
form.is_valid()
print(form.validated_data) # dict_values([[<User: root@root.com>]]) There, I found it. A minimal case demonstrating the difference in |
I think I found out where the problem is. In django.forms.models.py:ModelMultipleChoiceField.clean it has def clean(self, value):
value = self.prepare_value(value)
if self.required and not value:
raise ValidationError(self.error_messages["required"], code="required")
elif not self.required and not value:
return self.queryset.none()
qs = self._check_values(value)
self.run_validators(value)
return qs The two return paths are either The former is where the empty queryset comes from.
def _check_values(self, value):
...
result = list(super()._check_values(value)) # This is where the list vs queryset comes from
result += [self.null_value] if null else []
return result we can clearly see the Solutionclass ModelMultipleChoiceFilter(QuerySetRequestMixin, MultipleChoiceFilter):
field_class = ModelMultipleChoiceField
def clean(self, value):
# When a 'method' argument is passed, the proxy FilterMethod class is used
# and first checks if the value is in EMPTY_VALUES, calling 'method' only if it is not.
# When value is empty, super() returns an empty queryset. `value in EMPTY_VALUES is False`.
# When value is not empty, super() returns a list. `value in EMPTY_VALUES is True`.
#
# The inconsistency is fixed by calling list() on whatever super() returns. That way
# FilterMethod will always get a list and `value in EMPTY_VALUES` will work as intended.
return list(super().clean(value)) |
Here's my filterset:
If I GET
/api/locks/
,/api/locks/?
, or/api/locks/?grate_widths=
myfilter_grate_widths
method is called. Thevalue
is<Queryset []>
.Examining the call stack we see that it came from
EMPTY_VALUES is
([], (), {}, "", None)
.But of course the issue is that
value
is<Queryset []>
, which is not inEMPTY_VALUES
.I did some digging and this value is generated by the form. The form is django.forms.Form looking at the metaclass code. So I am not sure if this bug is django-filter or django itself.
Yes I can fix this by adding
if not value: return queryset
, but the source code inFilteMethod
suggests that my method should only be called when there is some non-empty value, which my url parameters are (empty).What am I using
The text was updated successfully, but these errors were encountered: