29👍
I stumbled across this question while trying to solve a nearly identical problem to yourself, and while I could have just written a custom filter, your question got me intrigued and I had to dig deeper!
It turns out that a ModelMultipleChoiceFilter
only makes one change over a normal Filter
, as seen in the django_filters
source code below:
class ModelChoiceFilter(Filter):
field_class = forms.ModelChoiceField
class ModelMultipleChoiceFilter(MultipleChoiceFilter):
field_class = forms.ModelMultipleChoiceField
That is, it changes the field_class
to a ModelMultipleChoiceField
from Django’s built in forms.
Taking a look at the source code for ModelMultipleChoiceField
, one of the required arguments to __init__()
is queryset
, so you were on the right track there.
The other piece of the puzzle comes from the ModelMultipleChoiceField.clean()
method, with a line: key = self.to_field_name or 'pk'
. What this means is that by default it will take whatever value you pass to it (eg.,"cooking"
) and try to look up Tag.objects.filter(pk="cooking")
, when obviously we want it to look at the name, and as we can see in that line, what field it compares to is controlled by self.to_field_name
.
Luckily, django_filters
‘s Filter.field()
method includes the following when instantiating the actual field.
self._field = self.field_class(required=self.required,
label=self.label, widget=self.widget, **self.extra)
Of particular note is the **self.extra
, which comes from Filter.__init__()
: self.extra = kwargs
, so all we need to do is pass an extra to_field_name
kwarg to the ModelMultipleChoiceFilter
and it will be handed through to the underlying ModelMultipleChoiceField
.
So (skip here for the actual solution!), the actual code you want is
tags = django_filters.ModelMultipleChoiceFilter(
name='sitetags__name',
to_field_name='name',
lookup_type='in',
queryset=SiteTag.objects.all()
)
So you were really close with the code you posted above! I don’t know if this solution will be relevant to you anymore, but hopefully it might help someone else in the future!
0👍
The solution that worked for me was to use a MultipleChoiceFilter
. In my case, I have judges that have races, and I want my API to let people query for, say, either black or white judges.
The filter ends up being:
race = filters.MultipleChoiceFilter(
choices=Race.RACES,
action=lambda queryset, value:
queryset.filter(race__race__in=value)
)
Race
is a many to many field off of Judge
:
class Race(models.Model):
RACES = (
('w', 'White'),
('b', 'Black or African American'),
('i', 'American Indian or Alaska Native'),
('a', 'Asian'),
('p', 'Native Hawaiian or Other Pacific Islander'),
('h', 'Hispanic/Latino'),
)
race = models.CharField(
choices=RACES,
max_length=5,
)
I’m not a huge fan of lambda
functions usually, but it made sense here because it’s such a small function. Basically, this sets up a MultipleChoiceFilter
that passes the values from the GET parameters to the race
field of the Race
model. They’re passed in as a list, so that’s why the in
parameter works.
So, my users can do:
/api/judges/?race=w&race=b
And they’ll get back judges that have identified as either black or white.
PS: Yes, I recognize that this isn’t the entire set of possible races. But it is what the U.S. census collects!
- Django AttributeError 'tuple' object has no attribute 'regex'
- Is there any list of blog engines, written in Django?
- Create a canonical "parent" product in Django Oscar programmatically
- Uwsgi segmentation fault when serving a django application