Skip to content

Commit e45708b

Browse files
authored
Merge pull request #492 from jayhale/django-filter-2
Make GrapheneFilterSetMixin compatible with django-filter 2
2 parents 14f156e + 0314931 commit e45708b

File tree

7 files changed

+65
-63
lines changed

7 files changed

+65
-63
lines changed

.travis.yml

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@ install:
1111
pip install -e .[test]
1212
pip install psycopg2 # Required for Django postgres fields testing
1313
pip install django==$DJANGO_VERSION
14-
if (($(echo "$DJANGO_VERSION <= 1.9" | bc -l))); then # DRF dropped 1.8 and 1.9 support at 3.7.0
15-
pip install djangorestframework==3.6.4
16-
fi
1714
python setup.py develop
1815
elif [ "$TEST_TYPE" = lint ]; then
1916
pip install flake8
@@ -44,13 +41,13 @@ matrix:
4441
env: TEST_TYPE=build DJANGO_VERSION=2.0
4542
- python: '3.6'
4643
env: TEST_TYPE=build DJANGO_VERSION=2.0
44+
- python: '3.5'
45+
env: TEST_TYPE=build DJANGO_VERSION=2.1
46+
- python: '3.6'
47+
env: TEST_TYPE=build DJANGO_VERSION=2.1
4748
- python: '2.7'
48-
env: TEST_TYPE=build DJANGO_VERSION=1.8
49-
- python: '2.7'
50-
env: TEST_TYPE=build DJANGO_VERSION=1.9
51-
- python: '2.7'
52-
env: TEST_TYPE=build DJANGO_VERSION=1.10
53-
- python: '2.7'
49+
env: TEST_TYPE=lint
50+
- python: '3.6'
5451
env: TEST_TYPE=lint
5552
deploy:
5653
provider: pypi

docs/filtering.rst

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@ Filtering
22
=========
33

44
Graphene integrates with
5-
`django-filter <https://django-filter.readthedocs.io/en/1.1.0/>`__ (< 2.0.0) to provide
6-
filtering of results (this also means filtering is only compatible with Django < 2.0).
7-
8-
See the `usage
9-
documentation <https://django-filter.readthedocs.io/en/1.1.0/guide/usage.html#the-filter>`__
5+
`django-filter <https://django-filter.readthedocs.io/en/master/>`__ (2.x for
6+
Python 3 or 1.x for Python 2) to provide filtering of results. See the `usage
7+
documentation <https://django-filter.readthedocs.io/en/master/guide/usage.html#the-filter>`__
108
for details on the format for ``filter_fields``.
119

1210
This filtering is automatically available when implementing a ``relay.Node``.
@@ -17,7 +15,7 @@ You will need to install it manually, which can be done as follows:
1715
.. code:: bash
1816
1917
# You'll need to django-filter
20-
pip install django-filter==1.1.0
18+
pip install django-filter>=2
2119
2220
Note: The techniques below are demoed in the `cookbook example
2321
app <https://github.com/graphql-python/graphene-django/tree/master/examples/cookbook>`__.
@@ -28,7 +26,7 @@ Filterable fields
2826
The ``filter_fields`` parameter is used to specify the fields which can
2927
be filtered upon. The value specified here is passed directly to
3028
``django-filter``, so see the `filtering
31-
documentation <https://django-filter.readthedocs.io/en/1.1.0/guide/usage.html#the-filter>`__
29+
documentation <https://django-filter.readthedocs.io/en/master/guide/usage.html#the-filter>`__
3230
for full details on the range of options available.
3331

3432
For example:
@@ -129,7 +127,7 @@ create your own ``Filterset`` as follows:
129127
all_animals = DjangoFilterConnectionField(AnimalNode,
130128
filterset_class=AnimalFilter)
131129
132-
The context argument is passed on as the `request argument <http://django-filter.readthedocs.io/en/1.1.0/guide/usage.html#request-based-filtering>`__
130+
The context argument is passed on as the `request argument <http://django-filter.readthedocs.io/en/master/guide/usage.html#request-based-filtering>`__
133131
in a ``django_filters.FilterSet`` instance. You can use this to customize your
134132
filters to be context-dependent. We could modify the ``AnimalFilter`` above to
135133
pre-filter animals owned by the authenticated user (set in ``context.user``).

examples/cookbook/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ graphene
22
graphene-django
33
graphql-core>=2.1rc1
44
django==1.9
5-
django-filter<2
5+
django-filter>=2

graphene_django/compat.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,7 @@ class MissingType(object):
55
try:
66
# Postgres fields are only available in Django with psycopg2 installed
77
# and we cannot have psycopg2 on PyPy
8-
from django.contrib.postgres.fields import ArrayField, HStoreField, RangeField
8+
from django.contrib.postgres.fields import (ArrayField, HStoreField,
9+
JSONField, RangeField)
910
except ImportError:
1011
ArrayField, HStoreField, JSONField, RangeField = (MissingType,) * 4
11-
12-
13-
try:
14-
# Postgres fields are only available in Django 1.9+
15-
from django.contrib.postgres.fields import JSONField
16-
except ImportError:
17-
JSONField = MissingType

graphene_django/filter/filterset.py

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import itertools
22

33
from django.db import models
4-
from django.utils.text import capfirst
5-
from django_filters import Filter, MultipleChoiceFilter
4+
from django_filters import Filter, MultipleChoiceFilter, VERSION
65
from django_filters.filterset import BaseFilterSet, FilterSet
76
from django_filters.filterset import FILTER_FOR_DBFIELD_DEFAULTS
87

@@ -15,7 +14,10 @@ class GlobalIDFilter(Filter):
1514
field_class = GlobalIDFormField
1615

1716
def filter(self, qs, value):
18-
_type, _id = from_global_id(value)
17+
""" Convert the filter value to a primary key before filtering """
18+
_id = None
19+
if value is not None:
20+
_, _id = from_global_id(value)
1921
return super(GlobalIDFilter, self).filter(qs, _id)
2022

2123

@@ -32,36 +34,52 @@ def filter(self, qs, value):
3234
models.OneToOneField: {"filter_class": GlobalIDFilter},
3335
models.ForeignKey: {"filter_class": GlobalIDFilter},
3436
models.ManyToManyField: {"filter_class": GlobalIDMultipleChoiceFilter},
37+
models.ManyToOneRel: {"filter_class": GlobalIDMultipleChoiceFilter},
38+
models.ManyToManyRel: {"filter_class": GlobalIDMultipleChoiceFilter},
3539
}
3640

3741

3842
class GrapheneFilterSetMixin(BaseFilterSet):
43+
""" A django_filters.filterset.BaseFilterSet with default filter overrides
44+
to handle global IDs """
45+
3946
FILTER_DEFAULTS = dict(
4047
itertools.chain(
41-
FILTER_FOR_DBFIELD_DEFAULTS.items(), GRAPHENE_FILTER_SET_OVERRIDES.items()
48+
FILTER_FOR_DBFIELD_DEFAULTS.items(),
49+
GRAPHENE_FILTER_SET_OVERRIDES.items()
4250
)
4351
)
4452

45-
@classmethod
46-
def filter_for_reverse_field(cls, f, name):
47-
"""Handles retrieving filters for reverse relationships
48-
49-
We override the default implementation so that we can handle
50-
Global IDs (the default implementation expects database
51-
primary keys)
52-
"""
53-
try:
54-
rel = f.field.remote_field
55-
except AttributeError:
56-
rel = f.field.rel
57-
58-
default = {"name": name, "label": capfirst(rel.related_name)}
59-
if rel.multiple:
60-
# For to-many relationships
61-
return GlobalIDMultipleChoiceFilter(**default)
62-
else:
63-
# For to-one relationships
64-
return GlobalIDFilter(**default)
53+
54+
# To support a Django 1.11 + Python 2.7 combination django-filter must be
55+
# < 2.x.x. To support the earlier version of django-filter, the
56+
# filter_for_reverse_field method must be present on GrapheneFilterSetMixin and
57+
# must not be present for later versions of django-filter.
58+
if VERSION[0] < 2:
59+
from django.utils.text import capfirst
60+
61+
class GrapheneFilterSetMixinPython2(GrapheneFilterSetMixin):
62+
63+
@classmethod
64+
def filter_for_reverse_field(cls, f, name):
65+
"""Handles retrieving filters for reverse relationships
66+
We override the default implementation so that we can handle
67+
Global IDs (the default implementation expects database
68+
primary keys)
69+
"""
70+
try:
71+
rel = f.field.remote_field
72+
except AttributeError:
73+
rel = f.field.rel
74+
default = {"name": name, "label": capfirst(rel.related_name)}
75+
if rel.multiple:
76+
# For to-many relationships
77+
return GlobalIDMultipleChoiceFilter(**default)
78+
else:
79+
# For to-one relationships
80+
return GlobalIDFilter(**default)
81+
82+
GrapheneFilterSetMixin = GrapheneFilterSetMixinPython2
6583

6684

6785
def setup_filterset(filterset_class):

graphene_django/tests/test_converter.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -237,16 +237,12 @@ class Meta:
237237

238238

239239
def test_should_manytoone_convert_connectionorlist():
240-
# Django 1.9 uses 'rel', <1.9 uses 'related
241-
related = getattr(Reporter.articles, "rel", None) or getattr(
242-
Reporter.articles, "related"
243-
)
244-
245240
class A(DjangoObjectType):
246241
class Meta:
247242
model = Article
248243

249-
graphene_field = convert_django_field(related, A._meta.registry)
244+
graphene_field = convert_django_field(Reporter.articles.rel,
245+
A._meta.registry)
250246
assert isinstance(graphene_field, graphene.Dynamic)
251247
dynamic_field = graphene_field.get_type()
252248
assert isinstance(dynamic_field, graphene.Field)
@@ -255,14 +251,12 @@ class Meta:
255251

256252

257253
def test_should_onetoone_reverse_convert_model():
258-
# Django 1.9 uses 'rel', <1.9 uses 'related
259-
related = getattr(Film.details, "rel", None) or getattr(Film.details, "related")
260-
261254
class A(DjangoObjectType):
262255
class Meta:
263256
model = FilmDetails
264257

265-
graphene_field = convert_django_field(related, A._meta.registry)
258+
graphene_field = convert_django_field(Film.details.related,
259+
A._meta.registry)
266260
assert isinstance(graphene_field, graphene.Dynamic)
267261
dynamic_field = graphene_field.get_type()
268262
assert isinstance(dynamic_field, graphene.Field)

setup.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"coveralls",
2020
"mock",
2121
"pytz",
22-
"django-filter<2",
22+
"django-filter<2;python_version<'3'",
23+
"django-filter>=2;python_version>='3'",
2324
"pytest-django>=3.3.2",
2425
] + rest_framework_require
2526

@@ -39,9 +40,9 @@
3940
"Programming Language :: Python :: 2",
4041
"Programming Language :: Python :: 2.7",
4142
"Programming Language :: Python :: 3",
42-
"Programming Language :: Python :: 3.3",
4343
"Programming Language :: Python :: 3.4",
4444
"Programming Language :: Python :: 3.5",
45+
"Programming Language :: Python :: 3.6",
4546
"Programming Language :: Python :: Implementation :: PyPy",
4647
],
4748
keywords="api graphql protocol rest relay graphene",
@@ -50,7 +51,7 @@
5051
"six>=1.10.0",
5152
"graphene>=2.1,<3",
5253
"graphql-core>=2.1rc1",
53-
"Django>=1.8.0",
54+
"Django>=1.11",
5455
"iso8601",
5556
"singledispatch>=3.4.0.3",
5657
"promise>=2.1",

0 commit comments

Comments
 (0)