Using 'django-filter' with CHOICES field - need "Any" option Using 'django-filter' with CHOICES field - need "Any" option django django

Using 'django-filter' with CHOICES field - need "Any" option


DRY'er would be to use the already defined 'choices' argument for ChoiceFilter.

So you could simply extend your FILTER_CHOICES to be your TICKET_STATUS_CHOICES plus an 'any' option with the empty string:

FILTER_CHOICES = (    ('new', 'New'),    ('accepted', 'Accepted'),    ('assigned', 'Assigned'),    ('reopened', 'Reopened'),    ('closed', 'Closed'),    ('', 'Any'),)

And your TicketFilter would be:

class TicketFilter(django_filters.FilterSet):   status = django_filters.ChoiceFilter(choices=FILTER_CHOICES)   class Meta:      model = Ticket      fields = ['assigned_to']


As mentioned in the short but sweet 'usage' docs,

Filters also take any arbitrary keyword arguments which get passed onto the django.forms.Field constructor.

This didn't make a lot of sense until I looked a bit further. In the ./django-filter/docs/ref/ directory, there's filters.txt which describes the Filter Fields and what Model Fields they interact with by default. (I think I've got the language right here, if not, correct me).

So, we can see that ChoiceFilter is used for any field "with choices".

Hitting up the Django documentation, what's important here is Form Fields, and how they interact with Models. (Using Django Forms). So we find ChoiceField (http://docs.djangoproject.com/en/dev/ref/forms/fields/#choicefield) which says

Takes one extra required argument: ChoiceField.choices An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this field.

So you can pass it a list, not just the original Tuple choices set.

So this might not be pythonic or DRY, but here's how I changed the model in question:

class TicketFilter(django_filters.FilterSet):    class Meta:        model = Ticket        fields = ['assigned_to', 'priority', 'status']    def __init__(self, *args, **kwargs):        super(TicketFilter, self).__init__(*args, **kwargs)        self.filters['priority'].extra.update(            {                'choices': CHOICES_FOR_PRIORITY_FILTER            })

And above that, where my _CHOICES were defined, I defined this new one (mentioned above) and made sure to append the original choices to the end:

CHOICES_FOR_PRIORITY_FILTER = [    ('', 'Any'),]CHOICES_FOR_PRIORITY_FILTER.extend(list(TICKET_PRIORITY_CHOICES))

I'm using list() here because the original Choices were set up in a tuple, so I want to turn that into a list. Also, if you're getting a NoneType error, be sure you're not attempting to assign the 'return value' of .extend(), because there isn't one. I tripped on this, because I forgot that it was a method of a list, and not a function that returned a new list.

If you know of an easier, more DRY or "pythonic" way to do this, please let me know!


Even shorter is to use the empty label:

class TicketFilter(FilterSet):    status = ChoiceFilter(choices=Ticket.CHOICES, empty_label=ugettext_lazy(u'Any'))    class Meta:        model = Ticket        fields = ('status', )