From 4140a03e74575881ed46e17b8a81da6f505ecca3 Mon Sep 17 00:00:00 2001 From: michaldabski Date: Tue, 24 Nov 2015 22:31:30 +0000 Subject: [PATCH 1/5] Test filtering by null fk --- tests/filtersets/test_base.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/filtersets/test_base.py b/tests/filtersets/test_base.py index a41b118..883bfe7 100644 --- a/tests/filtersets/test_base.py +++ b/tests/filtersets/test_base.py @@ -231,3 +231,10 @@ def _test(fs, data, qs, expected, count): .exclude(name__icontains='jon')), 1 ) + _test( + WaiterFilterSet, + 'restaurant__isnull=True', + Waiter.objects.all(), + Waiter.objects.filter(restaurant__isnull=True), + 0 + ) From 0b0705538e3a5f8ed2e3591c00978d974f6fbb3f Mon Sep 17 00:00:00 2001 From: michaldabski Date: Tue, 24 Nov 2015 23:15:37 +0000 Subject: [PATCH 2/5] Fallback to super implementation of get_spec() for isnull extract fk field filters to a constant --- url_filter/filtersets/base.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/url_filter/filtersets/base.py b/url_filter/filtersets/base.py index 96d13e1..4a0f716 100644 --- a/url_filter/filtersets/base.py +++ b/url_filter/filtersets/base.py @@ -20,6 +20,10 @@ __all__ = ['FilterSet', 'FilterSetOptions', 'StrictMode'] +FK_FIELD_FILTERS = { + 'isnull', +} + class StrictMode(enum.Enum): """ @@ -154,6 +158,7 @@ def _init(self, data=None, queryset=None, context=None, self.queryset = queryset self.context = context or {} self.strict_mode = strict_mode + self._given_lookups = None def repr(self, prefix=''): header = '{name}()'.format(name=self.__class__.__name__) @@ -347,7 +352,10 @@ def get_spec(self, config): value = LookupConfig(config.key, config.data) if name not in self.filters: - raise SkipFilter + if name in FK_FIELD_FILTERS: + return super(FilterSet, self).get_spec(config) + else: + raise SkipFilter return self.filters[name].get_spec(value) From 2ec63b88475c60f5c61d8400d8c332ee420de0fb Mon Sep 17 00:00:00 2001 From: michaldabski Date: Tue, 24 Nov 2015 23:19:48 +0000 Subject: [PATCH 3/5] Create a waiter with no restaurant to test isnull --- test_project/one_to_one/fixtures/one_to_one.json | 7 +++++++ test_project/one_to_one/models.py | 2 +- tests/filtersets/test_base.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/test_project/one_to_one/fixtures/one_to_one.json b/test_project/one_to_one/fixtures/one_to_one.json index b53f364..4e627f5 100644 --- a/test_project/one_to_one/fixtures/one_to_one.json +++ b/test_project/one_to_one/fixtures/one_to_one.json @@ -54,5 +54,12 @@ "name": "Steve" }, "pk": 3 +}, +{ + "model": "one_to_one.waiter", + "fields": { + "name": "Michal" + }, + "pk": 4 } ] diff --git a/test_project/one_to_one/models.py b/test_project/one_to_one/models.py index a2e4377..e0eb7f6 100644 --- a/test_project/one_to_one/models.py +++ b/test_project/one_to_one/models.py @@ -26,7 +26,7 @@ def __str__(self): @six.python_2_unicode_compatible class Waiter(models.Model): - restaurant = models.ForeignKey(Restaurant) + restaurant = models.ForeignKey(Restaurant, null=True) name = models.CharField(max_length=50) def __str__(self): diff --git a/tests/filtersets/test_base.py b/tests/filtersets/test_base.py index 883bfe7..91580d1 100644 --- a/tests/filtersets/test_base.py +++ b/tests/filtersets/test_base.py @@ -236,5 +236,5 @@ def _test(fs, data, qs, expected, count): 'restaurant__isnull=True', Waiter.objects.all(), Waiter.objects.filter(restaurant__isnull=True), - 0 + 1 ) From 37c3af36e5ad72fc777ada6fd67c430cbf26802e Mon Sep 17 00:00:00 2001 From: michaldabski Date: Wed, 25 Nov 2015 20:17:09 +0000 Subject: [PATCH 4/5] Test filtering by isnull=False --- tests/filtersets/test_base.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/filtersets/test_base.py b/tests/filtersets/test_base.py index 91580d1..397cfe3 100644 --- a/tests/filtersets/test_base.py +++ b/tests/filtersets/test_base.py @@ -238,3 +238,10 @@ def _test(fs, data, qs, expected, count): Waiter.objects.filter(restaurant__isnull=True), 1 ) + _test( + WaiterFilterSet, + 'restaurant__isnull=False', + Waiter.objects.all(), + Waiter.objects.filter(restaurant__isnull=False), + 3 + ) From 2f4241abfe1603d30131a47ea21f8dac130a078d Mon Sep 17 00:00:00 2001 From: michaldabski Date: Wed, 25 Nov 2015 20:36:03 +0000 Subject: [PATCH 5/5] Make isnull boolean field non-required implicit required=True caused filters with value false be ignored since true value was required, thus making it impossible to filter by isnull=False. --- url_filter/filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/url_filter/filters.py b/url_filter/filters.py index b538594..7289a64 100644 --- a/url_filter/filters.py +++ b/url_filter/filters.py @@ -17,7 +17,7 @@ } LOOKUP_FIELD_OVERWRITES = { - 'isnull': forms.BooleanField(), + 'isnull': forms.BooleanField(required=False), 'second': forms.IntegerField(min_value=0, max_value=59), 'minute': forms.IntegerField(min_value=0, max_value=59), 'hour': forms.IntegerField(min_value=0, max_value=23),