I'm using the very cool django-filter (via: http://github.com/alex/django-filter) and either can't seem to wrap my head around the docs, or maybe just need a little boost.
When I show the filter form on an object list page, for a FK field I get the drop down that includes a "-----" which results in an "any" type filter. But I have some choices set to a field on that model, and I'd like to get the same "any" type option. Here's a relevant example portion from models.py:
TICKET_STATUS_CHOICES = (
('new', 'New'),
('accepted', 'Accepted'),
('assigned', 'Assigned'),
('reopened', 'Reopened'),
('closed', 'Closed'),
)
class Ticket(models.Model):
assigned_to = models.ForeignKey(User, null=True, blank=True)
status = models.CharField(max_length=20,
choices=TICKET_STATUS_CHOICES, default='new')
import django_filters
class TicketFilter(django_filters.FilterSet):
class Meta:
model = Ticket
fields = ['assigned_to', 'status']
When I display the filter form, 'assigned_to'
gets an 'any' option, as
well as listing the available users. The 'status'
field, however, is
limited to only the options listed in the actual '_CHOICES'.
How do I add an 'any' option to the fields based on _CHOICES?
Django-filter is a generic, reusable application to alleviate writing some of the more mundane bits of view code. Specifically, it allows users to filter down a queryset based on a model's fields, displaying the form to let them do this.
Basically use get() when you want to get a single unique object, and filter() when you want to get all objects that match your lookup parameters. Show activity on this post. you will get: Pol does not exist. I.e. If you want to run some code depending on whether a single object can be found, use filter.
Nope. Django filters operate at the database level, generating SQL. To filter based on Python properties, you have to load the object into Python to evaluate the property--and at that point, you've already done all the work to load it.
Django provides a filter() method which returns a subset of data. It accepts field names as keyword arguments and returns a QuerySet object. As database has only one record where name is 'tom' , the QuerySet object contains only a single record.
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', )
Based on the work of anonymous coward, I did the following:
import django_filters
from .models import Experiment
EMPTY_CHOICE = ('', '---------'), # Don't forget the trailing comma
class ExperimentFilter(django_filters.FilterSet):
class Meta:
model = Experiment
fields = ['method__year',]
def __init__(self, *args, **kwargs):
super(ExperimentFilter, self).__init__(*args, **kwargs)
self.filters['method__year'].extra['choices'] = EMPTY_CHOICE + \
self.filters['method__year'].extra['choices']
I combined all the methods above since I disliked specifying all the fields that need to be updated and ended up with something like this:
import django_filters
from django.db import models
from django_filters.filters import ChoiceFilter
EMPTY_CHOICE = ('', '---------')
class TicketFilter(django_filters.FilterSet):
def __init__(self, *args, **kwargs):
super(TicketFilter, self).__init__(*args, **kwargs)
# add empty choice to all choice fields:
choices = filter(
lambda f: isinstance(self.filters[f], ChoiceFilter),
self.filters)
for field_name in choices:
extended_choices = ((EMPTY_CHOICE,) +
self.filters[field_name].extra['choices'])
self.filters[field_name].extra['choices'] = extended_choices
Which does the job.
You can simply use null_label
and null_value
, e.g.
my_field = MultipleChoiceFilter(choices=MY_CHOICES, null_label='Any', null_value='')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With