diff --git a/.bumpversion.cfg b/.bumpversion.cfg index c8432a9..3e15829 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.15.2 +current_version = 0.15.3 commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+))? diff --git a/CHANGES.rst b/CHANGES.rst index a474a67..2e50925 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,11 @@ +Version 0.15.3 (2016-10-17) +--------------------------- + +Adds compatibility for DRF (3.5+) get_schema_fields filter backend +introspection. + +* #492 Port get_schema_fields from DRF + Version 0.15.2 (2016-09-29) --------------------------- diff --git a/django_filters/__init__.py b/django_filters/__init__.py index cb72924..514ef04 100644 --- a/django_filters/__init__.py +++ b/django_filters/__init__.py @@ -3,7 +3,7 @@ from __future__ import absolute_import from .filterset import FilterSet from .filters import * -__version__ = '0.15.2' +__version__ = '0.15.3' def parse_version(version): diff --git a/django_filters/compat.py b/django_filters/compat.py index e7fa88b..cbac451 100644 --- a/django_filters/compat.py +++ b/django_filters/compat.py @@ -1,4 +1,6 @@ +from __future__ import absolute_import + import django from django.conf import settings @@ -12,6 +14,13 @@ except ImportError: is_crispy = 'crispy_forms' in settings.INSTALLED_APPS and crispy_forms +# coreapi only compatible with DRF 3.4+ +try: + from rest_framework.compat import coreapi +except ImportError: + coreapi = None + + def remote_field(field): """ https://docs.djangoproject.com/en/1.9/releases/1.9/#field-rel-changes diff --git a/django_filters/rest_framework/backends.py b/django_filters/rest_framework/backends.py index 4027ac2..37c7dfa 100644 --- a/django_filters/rest_framework/backends.py +++ b/django_filters/rest_framework/backends.py @@ -89,3 +89,15 @@ class DjangoFilterBackend(BaseFilterBackend): return template_render(template, context={ 'filter': filter_instance }) + + def get_schema_fields(self, view): + # This is not compatible with widgets where the query param differs from the + # filter's attribute name. Notably, this includes `MultiWidget`, where query + # params will be of the format `_0`, `_1`, etc... + assert compat.coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' + filter_class = self.get_filter_class(view, view.get_queryset()) + + return [] if not filter_class else [ + compat.coreapi.Field(name=field_name, required=False, location='query') + for field_name in filter_class().filters.keys() + ] diff --git a/docs/conf.py b/docs/conf.py index 752c549..befb110 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -48,9 +48,9 @@ copyright = u'2013, Alex Gaynor and others.' # built documents. # # The short X.Y version. -version = '0.15.2' +version = '0.15.3' # The full version, including alpha/beta/rc tags. -release = '0.15.2' +release = '0.15.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/ref/filterset.txt b/docs/ref/filterset.txt index b934327..a4d851b 100644 --- a/docs/ref/filterset.txt +++ b/docs/ref/filterset.txt @@ -12,6 +12,8 @@ Meta options - :ref:`order_by ` - :ref:`form
` - :ref:`together ` +- filter_overrides +- :ref:`strict ` .. _model: @@ -120,7 +122,7 @@ This lets you override the displayed names for your ordering fields:: Note that the default query parameter name used for ordering is ``o``. You can override this by setting an ``order_by_field`` attribute on the -``FilterSet`` class to the string value you would like to use. +``FilterSet``'s Meta class to the string value you would like to use. .. _form: @@ -152,18 +154,6 @@ field set must either be all or none present in the request for fields = ['price', 'release_date', 'rating'] together = ['rating', 'price'] - -Non-Meta options ----------------- - -Note that these options do not go in the Meta class, they are specified directly -in your FilterSet class. - -- filter_overrides -- order_by_field -- :ref:`strict ` - - .. _strict: ``strict`` diff --git a/requirements/test-ci.txt b/requirements/test-ci.txt index 85ceb08..2c79c1d 100644 --- a/requirements/test-ci.txt +++ b/requirements/test-ci.txt @@ -1,3 +1,5 @@ +coreapi + coverage mock pytz diff --git a/requirements/test.txt b/requirements/test.txt index 5b885fa..b811569 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,3 +1,3 @@ --r travis-ci.txt +-r test-ci.txt django djangorestframework diff --git a/setup.py b/setup.py index 2ee6683..457d132 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ f = open('README.rst') readme = f.read() f.close() -version = '0.15.2' +version = '0.15.3' if sys.argv[-1] == 'publish': if os.system("pip freeze | grep wheel"): diff --git a/tests/rest_framework/test_backends.py b/tests/rest_framework/test_backends.py index f6b383c..763d093 100644 --- a/tests/rest_framework/test_backends.py +++ b/tests/rest_framework/test_backends.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import datetime from decimal import Decimal +from unittest import skipIf from django.conf.urls import url from django.test import TestCase @@ -17,7 +18,7 @@ except ImportError: from rest_framework import generics, serializers, status from rest_framework.test import APIRequestFactory -from django_filters import filters +from django_filters import compat, filters from django_filters.rest_framework import DjangoFilterBackend, FilterSet from django_filters.rest_framework import backends @@ -121,6 +122,35 @@ urlpatterns = [ ] +@skipIf(compat.coreapi is None, 'coreapi must be installed') +class GetSchemaFieldsTests(TestCase): + def test_fields_with_filter_fields_list(self): + backend = DjangoFilterBackend() + fields = backend.get_schema_fields(FilterFieldsRootView()) + fields = [f.name for f in fields] + + self.assertEqual(fields, ['decimal', 'date']) + + def test_fields_with_filter_fields_dict(self): + class DictFilterFieldsRootView(FilterFieldsRootView): + filter_fields = { + 'decimal': ['exact', 'lt', 'gt'], + } + + backend = DjangoFilterBackend() + fields = backend.get_schema_fields(DictFilterFieldsRootView()) + fields = [f.name for f in fields] + + self.assertEqual(fields, ['decimal', 'decimal__lt', 'decimal__gt']) + + def test_fields_with_filter_class(self): + backend = DjangoFilterBackend() + fields = backend.get_schema_fields(FilterClassRootView()) + fields = [f.name for f in fields] + + self.assertEqual(fields, ['text', 'decimal', 'date']) + + class CommonFilteringTestCase(TestCase): def _serialize_object(self, obj): return {'id': obj.id, 'text': obj.text, 'decimal': str(obj.decimal), 'date': obj.date.isoformat()}