MultipleChoiceFilter not behaving as I expected

1,842 views
Skip to first unread message

Richard Tier

unread,
Jan 21, 2014, 8:22:00 PM1/21/14
to django...@googlegroups.com
I would like to filter on EventModel by tag field. e.g, get all events where tags are "english" OR "maths".

I have considered MultipleChoiceFilter as it allows the OR capability.

However, this requires setting choice attribute in the filter- which is a but restrictive: I don't want to pull out all the tags in the db in order to populate the choices field.

I see AllValuesFilter exists, but the docs show this inherits from ChoiceField, so the lookup will be "english" AND "maths", rather than OR.

Aside from making a custom AllValueFilter, is there a built in way to achieve this? 

da...@gasmark6.com

unread,
Jan 22, 2014, 7:22:46 AM1/22/14
to django...@googlegroups.com
Hi Richard,

The choices keyword argument gets passed to the filter's form field, so you can explicitly specify the choices the same way one does for Django's form fields.


Something like this:

    TAG_CHOICES = [('english', 'English'), ('maths', 'Mathematics')]

    class EventFilterSet(FilterSet):
        tag = MultipleChoiceFilter(choices=TAG_CHOICES)

Of course if you want to find all the existing values for the tag field in your database then you will still need to do a query. This should be relatively efficient:

    known_tags = EventModel.objects.values_list('tag', flat=True).distinct()
    TAG_CHOICES = [(tag, tag) for known_tags]

Hope this helps,

David B.

Richard Tier

unread,
Jan 22, 2014, 3:50:25 PM1/22/14
to django...@googlegroups.com
Hi David,

Thanks for the reply.

Explicitly setting the choices look doable. However - if a tag gets added after the filter has been instantiated then it will not be included in choices.

Dynamically setting choices every time the filter is used looks rather overkill though.

For this reason I don't think explicitly stating the choices will be feasible. Please disagree if im wrong.

David Buxton

unread,
Jan 23, 2014, 9:27:54 AM1/23/14
to django...@googlegroups.com
Hi Richard,

If you want the choices to reflect the current values in the database when the filterset is instantiated then you will have to look them up every time the form is created.

This is a common problem with Django forms, and the usual solution is to have a form that gets the choices queryset when it is instantiated. You can write a custom form class that does this in its __init__, and then specify that as the form class to use in your FilterSet class:

    class EventFilterForm(forms.Form):
        def __init__(self, *args, **kwargs):
            super(EventFilterForm, self).__init__(*args, **kwargs)
            # Now get current tag choices.
            tags = Event.objects.values_list('tag', flat=True).distinct()
            tag_choices = [(tag, tag) for tag in tags]
            self.fields['tag'].choices = tag_choices

    class EventFilter(FilterSet):
        tag = MultipleChoiceFilter()

        class Meta:
            model = Event
            form = EventFilterForm

And Bob's your uncle!

You could also try using a ModelMultipleChoiceFilter with the tag queryset. This way you wouldn't need the custom form, but you would have to override the underlying model choice field somehow to let it use the string value to filter on the correct field (it would try filtering on the primary key by default).

Hope this helps, Dave

Richard Tier

unread,
Jan 23, 2014, 5:24:13 PM1/23/14
to django...@googlegroups.com
thanks David, MultipleChoiceFilter is great :). It worked out of the box for me as tag's primary key is it's name ("english", "maths", etc).


On Wednesday, January 22, 2014 1:22:00 AM UTC, Richard Tier wrote:

David Buxton

unread,
Jan 23, 2014, 5:33:11 PM1/23/14
to django...@googlegroups.com
Well that's great you solved your problem. Slightly confused by your answer - did you go for the custom form class like in my example? Or the ModelMultipleChoiceFilter with some kind of overriding of the key? I am guessing you meant the custom form class.

Either way, good news.

David

David Buxton

unread,
Jan 23, 2014, 5:48:15 PM1/23/14
to django...@googlegroups.com
(This might show up twice, sorry if it does.)

For performance you should tell Django to emit a database index on your event's tag field. 

Richard Tier

unread,
Jan 23, 2014, 7:05:46 PM1/23/14
to django...@googlegroups.com
great tip on db.

regarding clarification: I used non-custom ModelMultipleChoiceField:

    class EventFilter(django_filters.FilterSet):
        category = django_filters.CharFilter(name="category", lookup_type="eq")
        tags = django_filters.ModelMultipleChoiceFilter(
            name="tags",
            queryset=Tag.objects.all(),
            lookup_type='in'
        )

        class Meta:
            model = EventModel
            fields = ['category', 'tags']
Reply all
Reply to author
Forward
0 new messages