I came across this question, trying to solve an almost identical problem for myself, and although I could just write my own filter, your question made me intrigued, and I had to dig deeper!
It turns out that ModelMultipleChoiceFilter only makes one change over the normal Filter , as shown 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 field_class to ModelMultipleChoiceField from the built-in forms of Django.
Looking at the source code for ModelMultipleChoiceField , one of the required __init__() arguments is queryset , so you were on the right track there.
Another piece of the puzzle comes from the ModelMultipleChoiceField.clean() method, with a line: key = self.to_field_name or 'pk' . This means that by default it will take whatever value you pass it (for example, "cooking" ) and try to find Tag.objects.filter(pk="cooking") when we obviously want it to look at name, and, as we see, in this line, in which field it is compared, self.to_field_name controlled.
Fortunately, the django_filters Filter.field() method includes when creating an instance of the actual field.
self._field = self.field_class(required=self.required, label=self.label, widget=self.widget, **self.extra)
Of particular note **self.extra , which comes from Filter.__init__() : self.extra = kwargs , so all we need to do is pass the extra to_field_name kwarg to ModelMultipleChoiceFilter , and it will be passed below ModelMultipleChoiceField .
So (skip here for a real solution!) The code you need
tags = django_filters.ModelMultipleChoiceFilter( name='sitetags__name', to_field_name='name', lookup_type='in', queryset=SiteTag.objects.all() )
So you were very close with the code you provided above! I don’t know if this solution will be more relevant for you, but I hope this can help someone else in the future!