1. Luke Plant
  2. django-easyfilters

Commits

Luke Plant  committed a03970b

Big API change to Filter - made 'params' an argument to constructor.

This means that it doesn't have to be passed around everywhere,
and that choices_from_params() can run just once.

Also changed use of FilterOptions to just a dictionary.

  • Participants
  • Parent commits bffc2e7
  • Branches default

Comments (0)

Files changed (4)

File README.rst

View file
     {% endfor %}
 
 Customisation of the filters can be done using a tuple containing (field_name,
-FilterOptions), instead of just field_name::
+dict of options), instead of just field_name::
 
     class BookFilterSet(FilterSet):
         model = Book
         fields = [
             'binding',
-            ('genre', FilterOptions(order_by_count=True))
+            ('genre', dict(order_by_count=True))
         ]
 
 Done so far

File django_easyfilters/filters.py

View file
 
     ### Public interface ###
 
-    def __init__(self, field, model, **kwargs):
-        # State: Filter objects are created as class attributes of FilterSets,
-        # and so cannot carry any request specific state. They only have
-        # configuration information.
+    def __init__(self, field, model, params, **kwargs):
         self.field = field
         self.model = model
+        self.params = params
         if kwargs.get('query_param', None) is None:
             kwargs['query_param'] = field
         self.field_obj = self.model._meta.get_field(self.field)
         super(Filter, self).__init__(**kwargs)
+        # Make chosen an immutable sequence, to stop accidental mutation.
+        self.chosen = tuple(self.choices_from_params())
 
-    def apply_filter(self, qs, params):
+    def apply_filter(self, qs):
         """
         Apply the filtering defined in params (request.GET) to the queryset qs,
         returning the new QuerySet.
         """
-        p_vals = self.choices_from_params(params)
-        while len(p_vals) > 0:
-            lookup = self.lookup_from_choice(p_vals.pop())
+        choices = list(self.chosen)
+        while len(choices) > 0:
+            lookup = self.lookup_from_choice(choices.pop())
             qs = qs.filter(**lookup)
         return qs
 
-    def get_choices(self, qs, params):
+    def get_choices(self, qs):
         """
         Returns a list of namedtuples containing (label (as a string), count,
         params, link type)
 
     ### Methods that are used by base implementation above ###
 
-    def choices_from_params(self, params):
+    def choices_from_params(self):
         out = []
-        for p in params.getlist(self.query_param):
+        for p in self.params.getlist(self.query_param):
             try:
                 choice = self.choice_from_param(p)
                 out.append(choice)
 
     def param_from_choices(self, choices):
         """
-        For a list of choices, return the parameter that should be created.
+        For a list of choices, return the parameter list that should be created.
         """
         return map(unicode, choices)
 
-    def build_params(self, params, add=None, remove=None):
+    def build_params(self, add=None, remove=None):
         """
         Builds a new parameter MultiDict.
         add is an optional item to add,
         remove is an option list of items to remove.
         """
-        params = params.copy()
-        chosen = self.choices_from_params(params)
+        params = self.params.copy()
+        chosen = list(self.chosen)
         if remove is not None:
             for r in remove:
                 chosen.remove(r)
         params.pop('page', None) # links should reset paging
         return params
 
-    def sort_choices(self, qs, params, choices):
+    def sort_choices(self, qs, choices):
         """
         Sorts the choices by applying order_by_count if applicable.
 
         if self.order_by_count:
             choices.sort(key=operator.attrgetter('count'), reverse=True)
         else:
-            choices = self.sort_choices_custom(qs, params, choices)
+            choices = self.sort_choices_custom(qs, choices)
         return choices
 
-    def sort_choices_custom(self, qs, params, choices):
+    def sort_choices_custom(self, qs, choices):
         """
         Override this to provide a custom sorting method for a field. If sorting
         can be better done in the DB, it should be done in the get_choices_add
 
 class SingleValueFilterMixin(object):
 
-    def get_values_counts(self, qs, params):
+    def get_values_counts(self, qs):
         """
         Returns a SortedDict dictionary of {value: count}.
 
                                     params=None)]
         return choices
 
-    def get_choices(self, qs, params):
-        choices_remove = self.get_choices_remove(qs, params)
+    def get_choices(self, qs):
+        choices_remove = self.get_choices_remove(qs)
         if len(choices_remove) > 0:
             return choices_remove
         else:
-            choices_add = self.normalize_add_choices(self.get_choices_add(qs, params))
-            return self.sort_choices(qs, params, choices_add)
+            choices_add = self.normalize_add_choices(self.get_choices_add(qs))
+            return self.sort_choices(qs, choices_add)
 
-    def get_choices_add(self, qs, params):
+    def get_choices_add(self, qs):
         raise NotImplementedError()
 
-    def get_choices_remove(self, qs, params):
-        choices = self.choices_from_params(params)
+    def get_choices_remove(self, qs):
+        chosen = self.chosen
         return [FilterChoice(self.display_choice(choice),
                              None, # Don't need count for removing
-                             self.build_params(params, remove=[choice]),
+                             self.build_params(remove=[choice]),
                              FILTER_REMOVE)
-                for choice in choices]
+                for choice in chosen]
 
 
 class ValuesFilter(SingleValueFilterMixin, Filter):
         else:
             return retval
 
-    def get_choices_add(self, qs, params):
+    def get_choices_add(self, qs):
         """
         Called by 'get_choices', this is usually the one to override.
         """
-        count_dict = self.get_values_counts(qs, params)
+        count_dict = self.get_values_counts(qs)
         return [FilterChoice(self.display_choice(val),
                              count,
-                             self.build_params(params, add=val),
+                             self.build_params(add=val),
                              FILTER_ADD)
                 for val, count in count_dict.items()]
 
         # 3) above
         return self.choices_dict.get(choice, choice)
 
-    def get_choices_add(self, qs, params):
-        count_dict = self.get_values_counts(qs, params)
+    def get_choices_add(self, qs):
+        count_dict = self.get_values_counts(qs)
         choices = []
         for val, display in self.field_obj.choices:
             # 1), 2) above
                 # call display_choice() in case it is overriden.
                 choices.append(FilterChoice(self.display_choice(val),
                                             count_dict[val],
-                                            self.build_params(params, add=val),
+                                            self.build_params(add=val),
                                             FILTER_ADD))
         return choices
 
         lookup = {self.rel_field.name: choice}
         return unicode(self.rel_model.objects.get(**lookup))
 
-    def get_choices_add(self, qs, params):
-        count_dict = self.get_values_counts(qs, params)
+    def get_choices_add(self, qs):
+        count_dict = self.get_values_counts(qs)
         lookup = {self.rel_field.name + '__in': count_dict.keys()}
         objs = self.rel_model.objects.filter(**lookup)
         choices = []
             pk = getattr(o, self.rel_field.attname)
             choices.append(FilterChoice(unicode(o),
                                         count_dict[pk],
-                                        self.build_params(params, add=pk),
+                                        self.build_params(add=pk),
                                         FILTER_ADD))
         return choices
 
 
 class MultiValueFilterMixin(object):
 
-    def get_choices(self, qs, params):
+    def get_choices(self, qs):
         # In general, can filter multiple times, so we can have multiple remove
         # links, and multiple add links, at the same time.
-        choices_remove = self.get_choices_remove(qs, params)
-        choices_add = self.get_choices_add(qs, params)
-        choices_add = self.sort_choices(qs, params, choices_add)
+        choices_remove = self.get_choices_remove(qs)
+        choices_add = self.get_choices_add(qs)
+        choices_add = self.sort_choices(qs, choices_add)
         return choices_remove + choices_add
 
 
         except ValidationError:
             raise ValueError()
 
-    def get_choices_add(self, qs, params):
+    def get_choices_add(self, qs):
         # It is easiest to base queries around the intermediate table, in order
         # to get counts.
         through = self.field_obj.rel.through
 
         # We need to exclude items in other table that we have already filtered
         # on, because they are not interesting.
-        exclude_filter = {fkey_to_other_table.name + '__in': self.choices_from_params(params)}
+        exclude_filter = {fkey_to_other_table.name + '__in': self.chosen}
         m2m_objs = m2m_objs.exclude(**exclude_filter)
 
         # Now get counts:
             pk = o.pk
             choices.append(FilterChoice(unicode(o),
                                         count_dict[pk],
-                                        self.build_params(params, add=pk),
+                                        self.build_params(add=pk),
                                         FILTER_ADD))
         return choices
 
-    def get_choices_remove(self, qs, params):
-        choices = self.choices_from_params(params)
+    def get_choices_remove(self, qs):
+        chosen = self.chosen
         # Do a query in bulk to get objs corresponding to choices.
-        objs = self.rel_model.objects.filter(pk__in=choices)
+        objs = self.rel_model.objects.filter(pk__in=chosen)
 
         # We want to preserve order of items in params, so use a dict:
         obj_dict = dict([(obj.pk, obj) for obj in objs])
         return [FilterChoice(unicode(obj_dict[choice]),
                              None, # Don't need count for removing
-                             self.build_params(params, remove=[choice]),
+                             self.build_params(remove=[choice]),
                              FILTER_REMOVE)
-                for choice in choices]
+                for choice in chosen]
 
 
 class DrillDownMixin(object):
 
-    def get_choices_remove(self, qs, params):
+    def get_choices_remove(self, qs):
         # Due to drill down, if an earlier param is removed,
         # the later params must be removed too.
-        chosen = self.choices_from_params(params)
+        chosen = list(self.chosen)
         out = []
         for i, choice in enumerate(chosen):
             out.append(FilterChoice(self.display_choice(choice),
                                     None,
-                                    self.build_params(params, remove=chosen[i:]),
+                                    self.build_params(remove=chosen[i:]),
                                     FILTER_REMOVE))
         return out
 
     def display_choice(self, choice):
         return choice.display()
 
-    def get_choices_add(self, qs, params):
-        chosen = self.choices_from_params(params)
+    def get_choices_add(self, qs):
+        chosen = list(self.chosen)
         range_type = None
 
         if len(chosen) > 0:
                 continue
             choices.append(FilterChoice(date_choice.display(),
                                         count,
-                                        self.build_params(params, add=date_choice),
+                                        self.build_params(add=date_choice),
                                         FILTER_ADD))
         return choices

File django_easyfilters/filterset.py

View file
         self.initial_queryset = queryset
         self.model = queryset.model
         self.filters = self.setup_filters()
-        self.qs = self.apply_filters(queryset, params)
+        self.qs = self.apply_filters(queryset)
 
-    def apply_filters(self, queryset, params):
+    def apply_filters(self, queryset):
         for f in self.filters:
-            queryset = f.apply_filter(queryset, params)
+            queryset = f.apply_filter(queryset)
         return queryset
 
-    def render_filter(self, filter_, qs, params):
+    def render_filter(self, filter_, qs):
         field_obj = self.model._meta.get_field(filter_.field)
-        choices = filter_.get_choices(qs, params)
+        choices = filter_.get_choices(qs)
         ctx = {'filterlabel': capfirst(field_obj.verbose_name)}
         ctx['remove_choices'] = [dict(label=non_breaking_spaces(c.label),
                                       url=u'?' + c.params.urlencode())
         return template.Template(self.template)
 
     def render(self):
-        return mark_safe(u'\n'.join(self.render_filter(f, self.qs, self.params) for f in self.filters))
+        return mark_safe(u'\n'.join(self.render_filter(f, self.qs) for f in self.filters))
 
     def get_fields(self):
         return self.fields
 
-    def get_filter_for_field(self, field, **kwargs):
+    def get_filter_for_field(self, field):
         f, model, direct, m2m = self.model._meta.get_field_by_name(field)
         if f.rel is not None:
             if m2m:
-                klass = ManyToManyFilter
+                return ManyToManyFilter
             else:
-                klass = ForeignKeyFilter
+                return ForeignKeyFilter
         elif f.choices:
-            klass = ChoicesFilter
+            return ChoicesFilter
         else:
             type_ = f.get_internal_type()
             if type_ == 'DateField' or type_ == 'DateTimeField':
-                klass = DateTimeFilter
+                return DateTimeFilter
             else:
-                klass = ValuesFilter
-        return klass(field, self.model, **kwargs)
+                return ValuesFilter
 
     def setup_filters(self):
         filters = []
         for i, f in enumerate(self.get_fields()):
             if isinstance(f, basestring):
-                f = self.get_filter_for_field(f)
+                opts = {}
+                field_name = f
             else:
-                # (field name, FilterOptions)
-                field = f[0]
-                opts = f[1].__dict__.copy()
-                f = self.get_filter_for_field(field, **opts)
-            filters.append(f)
+                opts = f[1]
+                field_name = f[0]
+            klass = self.get_filter_for_field(field_name)
+            filters.append(klass(field_name, self.model, self.params, **opts))
         return filters
 
     def __unicode__(self):

File django_easyfilters/tests/filterset.py

View file
         self.assertEqual(rendered, unicode(fs))
 
         # And when in 'already filtered' mode:
-        choice = fs.filters[0].get_choices(qs, QueryDict(''))[0]
+        choice = fs.filters[0].get_choices(qs)[0]
         fs_filtered = BookFilterSet(qs, choice.params)
         rendered_2 = fs_filtered.render()
         self.assertTrue('Genre' in rendered_2)
         new_g, created = Genre.objects.get_or_create(name='Nonsense')
         assert created
 
-        filter_ = ForeignKeyFilter('genre', Book)
+        data = MultiValueDict()
+        filter_ = ForeignKeyFilter('genre', Book, data)
         qs = Book.objects.all()
-        data = MultiValueDict()
 
-        choices = [(c.label, c.count) for c in filter_.get_choices(qs, data)]
+        choices = [(c.label, c.count) for c in filter_.get_choices(qs)]
 
         reached = [False, False]
         for g in Genre.objects.all():
         """
         qs = Book.objects.all()
         data = MultiValueDict()
-        filter_ = ForeignKeyFilter('genre', Book)
-        choices = filter_.get_choices(qs, data)
+        filter1 = ForeignKeyFilter('genre', Book, data)
+        choices = filter1.get_choices(qs)
 
         # If we use the params from e.g. the first choice, that should produce a
         # filtered qs when fed back in (i.e. when we 'click' on that option we
         reached = False
         for choice in choices:
             reached = True
-            qs_filtered = filter_.apply_filter(qs, choice.params)
+            filter2 = ForeignKeyFilter('genre', Book, choice.params)
+            qs_filtered = filter2.apply_filter(qs)
             self.assertEqual(len(qs_filtered), choice.count)
             for book in qs_filtered:
                 self.assertEqual(unicode(book.genre), choice.label)
         Ensure that a ForeignKey Filter will turn into a 'remove' link when an
         item has been selected.
         """
-        filter_ = ForeignKeyFilter('genre', Book)
         qs = Book.objects.all()
         data = MultiValueDict()
-        choices = filter_.get_choices(qs, data)
+        filter1 = ForeignKeyFilter('genre', Book, data)
+        choices = filter1.get_choices(qs)
         choice = choices[0]
 
-        qs_filtered = filter_.apply_filter(qs, choice.params)
-        choices2 = filter_.get_choices(qs_filtered, choice.params)
+        filter2 = ForeignKeyFilter('genre', Book, choice.params)
+        qs_filtered = filter2.apply_filter(qs)
+        choices2 = filter2.get_choices(qs_filtered)
 
         # Should have one item
         self.assertEqual(1, len(choices2))
         self.assertEqual(choices2[0].link_type, FILTER_REMOVE)
 
         # 'Clicking' should remove filtering
-        qs_reverted = filter_.apply_filter(qs, choices2[0].params)
+        filter3 = ForeignKeyFilter('genre', Book, choices2[0].params)
+        qs_reverted = filter3.apply_filter(qs)
         self.assertEqual(qs, qs_reverted)
 
     def test_values_filter(self):
         Tests for ValuesFilter
         """
         # We combine the tests for brevity
-        filter_ = ValuesFilter('edition', Book)
+        filter1 = ValuesFilter('edition', Book, MultiValueDict())
         qs = Book.objects.all()
-        choices = filter_.get_choices(qs, MultiValueDict())
+        choices = filter1.get_choices(qs)
 
         for choice in choices:
             count = Book.objects.filter(edition=choice.params.values()[0]).count()
             self.assertEqual(choice.count, count)
 
             # Check the filtering
-            qs_filtered = filter_.apply_filter(qs, choice.params)
+            filter2 = ValuesFilter('edition', Book, choice.params)
+            qs_filtered = filter2.apply_filter(qs)
             self.assertEqual(len(qs_filtered), choice.count)
             for book in qs_filtered:
                 self.assertEqual(unicode(book.edition), choice.label)
 
             # Check we've got a 'remove link' on filtered.
-            choices_filtered = filter_.get_choices(qs, choice.params)
+            choices_filtered = filter2.get_choices(qs)
             self.assertEqual(1, len(choices_filtered))
             self.assertEqual(choices_filtered[0].link_type, FILTER_REMOVE)
 
         """
         Tests for ChoicesFilter
         """
-        filter_ = ChoicesFilter('binding', Book)
+        filter1 = ChoicesFilter('binding', Book, MultiValueDict())
         qs = Book.objects.all()
-        choices = filter_.get_choices(qs, MultiValueDict())
+        choices = filter1.get_choices(qs)
         # Check:
         # - order is correct.
         # - all values present (guaranteed by fixture data)
         """
         Tests for ManyToManyFilter
         """
-        filter_ = ManyToManyFilter('authors', Book)
+        filter1 = ManyToManyFilter('authors', Book, MultiValueDict())
         qs = Book.objects.all()
 
         # ManyToMany can have 'drill down', i.e. multiple levels of filtering,
         # which can be removed individually.
 
         # First level:
-        choices = filter_.get_choices(qs, MultiValueDict())
+        choices = filter1.get_choices(qs)
 
         # Check list is full, and in right order
         self.assertEqual([unicode(v) for v in Author.objects.all()],
 
         for choice in choices:
             # For single choice, param will be single integer:
-            param = int(choice.params[filter_.query_param])
+            param = int(choice.params[filter1.query_param])
 
             # Check the count
             count = Book.objects.filter(authors=int(param)).count()
                              choice.label)
 
             # Check the filtering
-            qs_filtered = filter_.apply_filter(qs, choice.params)
+            filter2 = ManyToManyFilter('authors', Book, choice.params)
+            qs_filtered = filter2.apply_filter(qs)
             self.assertEqual(len(qs_filtered), choice.count)
 
             for book in qs_filtered:
                 self.assertTrue(author in book.authors.all())
 
             # Check we've got a 'remove link' on filtered.
-            choices_filtered = filter_.get_choices(qs, choice.params)
+            choices_filtered = filter2.get_choices(qs)
             self.assertEqual(choices_filtered[0].link_type, FILTER_REMOVE)
 
 
     def test_manytomany_filter_multiple(self):
-        filter_ = ManyToManyFilter('authors', Book)
         qs = Book.objects.all()
 
         # Specific example - multiple filtering
         # If we select 'emily' as an author:
 
         data =  MultiValueDict({'authors':[str(emily.pk)]})
-        qs_emily = filter_.apply_filter(qs, data)
+        filter1 = ManyToManyFilter('authors', Book, data)
+        qs_emily = filter1.apply_filter(qs)
 
         # ...we should get a qs that includes Poems and Wuthering Heights.
         self.assertTrue(qs_emily.filter(name='Poems').exists())
         self.assertFalse(qs_emily.filter(name='Jane Eyre').exists())
 
         # We should get a 'choices' that includes charlotte and anne
-        choices = filter_.get_choices(qs_emily, data)
+        choices = filter1.get_choices(qs_emily)
         self.assertTrue(unicode(anne) in [c.label for c in choices if c.link_type is FILTER_ADD])
         self.assertTrue(unicode(charlotte) in [c.label for c in choices if c.link_type is FILTER_ADD])
 
         self.assertTrue(unicode(emily) in [c.label for c in choices if c.link_type is FILTER_REMOVE])
 
         # If we select again:
-        data =  MultiValueDict({'authors': [str(emily.pk), str(anne.pk)]})
+        data2 =  MultiValueDict({'authors': [str(emily.pk), str(anne.pk)]})
+        filter2 = ManyToManyFilter('authors', Book, data2)
 
-        qs_emily_anne = filter_.apply_filter(qs, data)
+        qs_emily_anne = filter2.apply_filter(qs)
 
         # ...we should get a qs that includes Poems
         self.assertTrue(qs_emily_anne.filter(name='Poems').exists())
         # charlotte should have 'link_type' FILTER_ADD. Even though it
         # is the only choice, adding the choice is not necessarily the same as
         # not adding it (could have books by Rmily and Anne, but not charlotte)
-        choices = filter_.get_choices(qs_emily_anne, data)
+        choices = filter2.get_choices(qs_emily_anne)
         self.assertEqual([(c.label, c.link_type) for c in choices],
                          [(unicode(emily), FILTER_REMOVE),
                           (unicode(anne), FILTER_REMOVE),
         (and limit to max_links)
         """
         # This does drill down, and has multiple values.
-        f = DateTimeFilter('date_published', Book, max_links=10)
-
+        f = DateTimeFilter('date_published', Book, MultiValueDict(), max_links=10)
         qs = Book.objects.all()
 
         # We have enough data that it will not show a simple list of years.
-        choices = f.get_choices(qs, MultiValueDict())
+        choices = f.get_choices(qs)
         self.assertTrue(len(choices) <= 10)
 
     def test_datetime_filter_single_year_selected(self):
-        f = DateTimeFilter('date_published', Book, max_links=10)
+        params = MultiValueDict({'date_published':['1818']})
+        f = DateTimeFilter('date_published', Book, params, max_links=10)
         qs = Book.objects.all()
-        params = MultiValueDict({'date_published':['1818']})
 
         # Should get a number of books in queryset.
-        qs_filtered = f.apply_filter(qs, params)
+        qs_filtered = f.apply_filter(qs)
 
         self.assertEqual(list(qs_filtered),
                          list(qs.filter(date_published__year=1818)))
         # We only need 1 query if we've already told it what year to look at.
         with self.assertNumQueries(1):
-            choices = f.get_choices(qs_filtered, params)
+            choices = f.get_choices(qs_filtered)
 
         self.assertTrue(len([c for c in choices if c.link_type == FILTER_ADD]) >= 2)
         self.assertEqual(len([c for c in choices if c.link_type == FILTER_REMOVE]), 1)
 
     def test_datetime_filter_year_range_selected(self):
-        f = DateTimeFilter('date_published', Book, max_links=10)
+        params = MultiValueDict({'date_published':['1813..1814']})
+        f = DateTimeFilter('date_published', Book, params, max_links=10)
         qs = Book.objects.all()
-        params = MultiValueDict({'date_published':['1813..1814']})
 
         # Should get a number of books in queryset.
-        qs_filtered = f.apply_filter(qs, params)
+        qs_filtered = f.apply_filter(qs)
 
         start = date(1813, 1, 1)
         end = date(1815, 1, 1)
         # We only need 1 query if we've already told it what years to look at,
         # and there is data for both years.
         with self.assertNumQueries(1):
-            choices = f.get_choices(qs_filtered, params)
+            choices = f.get_choices(qs_filtered)
 
         self.assertEqual(len([c for c in choices if c.link_type == FILTER_REMOVE]), 1)
         self.assertEqual(len([c for c in choices if c.link_type == FILTER_ADD]), 2)
 
 
     def test_datetime_filter_invalid_query(self):
-        f = DateTimeFilter('date_published', Book, max_links=10)
+        params = MultiValueDict({'date_published':['1818xx']})
+        f = DateTimeFilter('date_published', Book, params, max_links=10)
+        f_empty = DateTimeFilter('date_published', Book, MultiValueDict(), max_links=10)
         qs = Book.objects.all()
-        params = MultiValueDict({'date_published':['1818xx']})
 
         # invalid param should be ignored
-        qs_filtered = f.apply_filter(qs, params)
+        qs_filtered = f.apply_filter(qs)
         self.assertEqual(list(qs_filtered),
                          list(qs))
 
-        self.assertEqual(list(f.get_choices(qs, params)),
-                         list(f.get_choices(qs, MultiValueDict({}))))
+        self.assertEqual(list(f.get_choices(qs)),
+                         list(f_empty.get_choices(qs)))
 
     def test_order_by_count(self):
         """
         Tests the 'order_by_count' option.
         """
-        filter1 = ForeignKeyFilter('genre', Book, order_by_count=True)
+        filter1 = ForeignKeyFilter('genre', Book, MultiValueDict(), order_by_count=True)
         qs = Book.objects.all()
-        choices1 = filter1.get_choices(qs, MultiValueDict())
+        choices1 = filter1.get_choices(qs)
 
         # Should be same after sorting by 'count'
         self.assertEqual(choices1, sorted(choices1, key=operator.attrgetter('count'), reverse=True))
 
-        filter2 = ForeignKeyFilter('genre', Book, order_by_count=False)
-        choices2 = filter2.get_choices(qs, MultiValueDict())
+        filter2 = ForeignKeyFilter('genre', Book, MultiValueDict(), order_by_count=False)
+        choices2 = filter2.get_choices(qs)
 
         # Should be same after sorting by 'label' (that is equal to Genre.name,
         # and Genre ordering is by that field)