diff --git a/django_filters/filters.py b/django_filters/filters.py index 7331bfd1d..d352af9eb 100644 --- a/django_filters/filters.py +++ b/django_filters/filters.py @@ -8,6 +8,7 @@ from django.utils.itercompat import is_iterable from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ +from django.contrib.postgres.forms import JSONField from .conf import settings from .constants import EMPTY_VALUES @@ -788,3 +789,7 @@ def method(self): (parent.__class__.__module__, parent.__class__.__name__, instance.method) return method + + +class JSONFilter(Filter): + field_class = JSONField() diff --git a/django_filters/filterset.py b/django_filters/filterset.py index a594a1229..21c7c86dd 100644 --- a/django_filters/filterset.py +++ b/django_filters/filterset.py @@ -9,6 +9,7 @@ ManyToOneRel, OneToOneRel ) +from django.contrib.postgres.fields import JSONField from .conf import settings from .constants import ALL_FIELDS @@ -26,7 +27,8 @@ ModelMultipleChoiceFilter, NumberFilter, TimeFilter, - UUIDFilter + UUIDFilter, + JSONFilter, ) from .utils import ( get_all_model_fields, @@ -130,6 +132,7 @@ def get_declared_filters(cls, bases, attrs): models.GenericIPAddressField: {'filter_class': CharFilter}, models.CommaSeparatedIntegerField: {'filter_class': CharFilter}, models.UUIDField: {'filter_class': UUIDFilter}, + JSONField: {'filter_class': JSONFilter}, # Forward relationships models.OneToOneField: { diff --git a/docs/ref/filters.txt b/docs/ref/filters.txt index 54d2ce8f0..0c51341b0 100644 --- a/docs/ref/filters.txt +++ b/docs/ref/filters.txt @@ -828,3 +828,9 @@ If you wish to sort by non-model fields, you'll need to add custom handling to a return ... return super(CustomOrderingFilter, self).filter(qs, value) + + +``JSONFilter`` +~~~~~~~~~~~~~~ + +This filter matches JSON values, used with ``models.JSONField`` by default. diff --git a/tests/models.py b/tests/models.py index 9a6f1040a..fdab2c657 100644 --- a/tests/models.py +++ b/tests/models.py @@ -1,5 +1,6 @@ from django import forms from django.db import models +from django.contrib.postgres.fields import JSONField from django.utils.translation import gettext_lazy as _ REGULAR = 0 @@ -102,6 +103,7 @@ class Book(models.Model): title = models.CharField(max_length=100) price = models.DecimalField(max_digits=6, decimal_places=2) average_rating = models.FloatField() + meta = JSONField() def __str__(self): return self.title diff --git a/tests/test_filtering.py b/tests/test_filtering.py index 18f2b4821..1430b27a6 100644 --- a/tests/test_filtering.py +++ b/tests/test_filtering.py @@ -27,7 +27,7 @@ OrderingFilter, RangeFilter, TimeRangeFilter, - TypedMultipleChoiceFilter + TypedMultipleChoiceFilter, ) from django_filters.filterset import FilterSet @@ -1990,3 +1990,26 @@ class Meta: f = F({'status': '2'}, queryset=qs) self.assertEqual(len(f.qs), 2) self.assertEqual(f.qs.count(), 2) + + +class JSONFilterTests(TestCase): + + def test_filtering(self): + b1 = Book.objects.create( + title="Ender's Game", price='1.00', average_rating=3.0, meta={"tag": 1}) + b2 = Book.objects.create( + title="Rainbow Six", price='1.00', average_rating=3.0, meta={"tag": 2}) + b3 = Book.objects.create( + title="Snowcrash", price='1.00', average_rating=3.0, meta={"tag2": 2}) + + class F(FilterSet): + class Meta: + model = Book + fields = ['meta'] + + qs = Book.objects.all() + f = F(queryset=qs) + self.assertQuerysetEqual(f.qs, [b1.pk, b2.pk, b3.pk], + lambda o: o.pk, ordered=False) + f = F({'meta__tag': 2}, queryset=qs) + self.assertQuerysetEqual(f.qs, [b2.pk], lambda o: o.pk) diff --git a/tests/test_filters.py b/tests/test_filters.py index 396f47274..9b7ff3069 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -7,6 +7,7 @@ from django.test import TestCase, override_settings from django.utils import translation from django.utils.translation import gettext as _ +from django.contrib.postgres.forms import JSONField from django_filters import filters, widgets from django_filters.fields import ( @@ -45,7 +46,8 @@ TimeFilter, TimeRangeFilter, TypedMultipleChoiceFilter, - UUIDFilter + UUIDFilter, + JSONFilter, ) from tests.models import Book, User @@ -1588,3 +1590,11 @@ def test_help_text(self): # regression test for #756 - the ususal CSV help_text is not relevant to ordering filters. self.assertEqual(OrderingFilter().field.help_text, '') self.assertEqual(OrderingFilter(help_text='a').field.help_text, 'a') + + +class JSONFilterTests(TestCase): + + def test_default_field(self): + f = JSONFilter() + field = f.field + self.assertIsInstance(field, JSONField)