Skip to content

Commit

Permalink
Merge pull request #492 from jayhale/django-filter-2
Browse files Browse the repository at this point in the history
Make GrapheneFilterSetMixin compatible with django-filter 2
  • Loading branch information
syrusakbary committed Sep 5, 2018
2 parents 14f156e + 0314931 commit e45708b
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 63 deletions.
15 changes: 6 additions & 9 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ install:
pip install -e .[test]
pip install psycopg2 # Required for Django postgres fields testing
pip install django==$DJANGO_VERSION
if (($(echo "$DJANGO_VERSION <= 1.9" | bc -l))); then # DRF dropped 1.8 and 1.9 support at 3.7.0
pip install djangorestframework==3.6.4
fi
python setup.py develop
elif [ "$TEST_TYPE" = lint ]; then
pip install flake8
Expand Down Expand Up @@ -44,13 +41,13 @@ matrix:
env: TEST_TYPE=build DJANGO_VERSION=2.0
- python: '3.6'
env: TEST_TYPE=build DJANGO_VERSION=2.0
- python: '3.5'
env: TEST_TYPE=build DJANGO_VERSION=2.1
- python: '3.6'
env: TEST_TYPE=build DJANGO_VERSION=2.1
- python: '2.7'
env: TEST_TYPE=build DJANGO_VERSION=1.8
- python: '2.7'
env: TEST_TYPE=build DJANGO_VERSION=1.9
- python: '2.7'
env: TEST_TYPE=build DJANGO_VERSION=1.10
- python: '2.7'
env: TEST_TYPE=lint
- python: '3.6'
env: TEST_TYPE=lint
deploy:
provider: pypi
Expand Down
14 changes: 6 additions & 8 deletions docs/filtering.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ Filtering
=========

Graphene integrates with
`django-filter <https://django-filter.readthedocs.io/en/1.1.0/>`__ (< 2.0.0) to provide
filtering of results (this also means filtering is only compatible with Django < 2.0).

See the `usage
documentation <https://django-filter.readthedocs.io/en/1.1.0/guide/usage.html#the-filter>`__
`django-filter <https://django-filter.readthedocs.io/en/master/>`__ (2.x for
Python 3 or 1.x for Python 2) to provide filtering of results. See the `usage
documentation <https://django-filter.readthedocs.io/en/master/guide/usage.html#the-filter>`__
for details on the format for ``filter_fields``.

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

For example:
Expand Down Expand Up @@ -129,7 +127,7 @@ create your own ``Filterset`` as follows:
all_animals = DjangoFilterConnectionField(AnimalNode,
filterset_class=AnimalFilter)
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>`__
The context argument is passed on as the `request argument <http://django-filter.readthedocs.io/en/master/guide/usage.html#request-based-filtering>`__
in a ``django_filters.FilterSet`` instance. You can use this to customize your
filters to be context-dependent. We could modify the ``AnimalFilter`` above to
pre-filter animals owned by the authenticated user (set in ``context.user``).
Expand Down
2 changes: 1 addition & 1 deletion examples/cookbook/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ graphene
graphene-django
graphql-core>=2.1rc1
django==1.9
django-filter<2
django-filter>=2
10 changes: 2 additions & 8 deletions graphene_django/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,7 @@ class MissingType(object):
try:
# Postgres fields are only available in Django with psycopg2 installed
# and we cannot have psycopg2 on PyPy
from django.contrib.postgres.fields import ArrayField, HStoreField, RangeField
from django.contrib.postgres.fields import (ArrayField, HStoreField,
JSONField, RangeField)
except ImportError:
ArrayField, HStoreField, JSONField, RangeField = (MissingType,) * 4


try:
# Postgres fields are only available in Django 1.9+
from django.contrib.postgres.fields import JSONField
except ImportError:
JSONField = MissingType
66 changes: 42 additions & 24 deletions graphene_django/filter/filterset.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import itertools

from django.db import models
from django.utils.text import capfirst
from django_filters import Filter, MultipleChoiceFilter
from django_filters import Filter, MultipleChoiceFilter, VERSION
from django_filters.filterset import BaseFilterSet, FilterSet
from django_filters.filterset import FILTER_FOR_DBFIELD_DEFAULTS

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

def filter(self, qs, value):
_type, _id = from_global_id(value)
""" Convert the filter value to a primary key before filtering """
_id = None
if value is not None:
_, _id = from_global_id(value)
return super(GlobalIDFilter, self).filter(qs, _id)


Expand All @@ -32,36 +34,52 @@ def filter(self, qs, value):
models.OneToOneField: {"filter_class": GlobalIDFilter},
models.ForeignKey: {"filter_class": GlobalIDFilter},
models.ManyToManyField: {"filter_class": GlobalIDMultipleChoiceFilter},
models.ManyToOneRel: {"filter_class": GlobalIDMultipleChoiceFilter},
models.ManyToManyRel: {"filter_class": GlobalIDMultipleChoiceFilter},
}


class GrapheneFilterSetMixin(BaseFilterSet):
""" A django_filters.filterset.BaseFilterSet with default filter overrides
to handle global IDs """

FILTER_DEFAULTS = dict(
itertools.chain(
FILTER_FOR_DBFIELD_DEFAULTS.items(), GRAPHENE_FILTER_SET_OVERRIDES.items()
FILTER_FOR_DBFIELD_DEFAULTS.items(),
GRAPHENE_FILTER_SET_OVERRIDES.items()
)
)

@classmethod
def filter_for_reverse_field(cls, f, name):
"""Handles retrieving filters for reverse relationships
We override the default implementation so that we can handle
Global IDs (the default implementation expects database
primary keys)
"""
try:
rel = f.field.remote_field
except AttributeError:
rel = f.field.rel

default = {"name": name, "label": capfirst(rel.related_name)}
if rel.multiple:
# For to-many relationships
return GlobalIDMultipleChoiceFilter(**default)
else:
# For to-one relationships
return GlobalIDFilter(**default)

# To support a Django 1.11 + Python 2.7 combination django-filter must be
# < 2.x.x. To support the earlier version of django-filter, the
# filter_for_reverse_field method must be present on GrapheneFilterSetMixin and
# must not be present for later versions of django-filter.
if VERSION[0] < 2:
from django.utils.text import capfirst

class GrapheneFilterSetMixinPython2(GrapheneFilterSetMixin):

@classmethod
def filter_for_reverse_field(cls, f, name):
"""Handles retrieving filters for reverse relationships
We override the default implementation so that we can handle
Global IDs (the default implementation expects database
primary keys)
"""
try:
rel = f.field.remote_field
except AttributeError:
rel = f.field.rel
default = {"name": name, "label": capfirst(rel.related_name)}
if rel.multiple:
# For to-many relationships
return GlobalIDMultipleChoiceFilter(**default)
else:
# For to-one relationships
return GlobalIDFilter(**default)

GrapheneFilterSetMixin = GrapheneFilterSetMixinPython2


def setup_filterset(filterset_class):
Expand Down
14 changes: 4 additions & 10 deletions graphene_django/tests/test_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,16 +237,12 @@ class Meta:


def test_should_manytoone_convert_connectionorlist():
# Django 1.9 uses 'rel', <1.9 uses 'related
related = getattr(Reporter.articles, "rel", None) or getattr(
Reporter.articles, "related"
)

class A(DjangoObjectType):
class Meta:
model = Article

graphene_field = convert_django_field(related, A._meta.registry)
graphene_field = convert_django_field(Reporter.articles.rel,
A._meta.registry)
assert isinstance(graphene_field, graphene.Dynamic)
dynamic_field = graphene_field.get_type()
assert isinstance(dynamic_field, graphene.Field)
Expand All @@ -255,14 +251,12 @@ class Meta:


def test_should_onetoone_reverse_convert_model():
# Django 1.9 uses 'rel', <1.9 uses 'related
related = getattr(Film.details, "rel", None) or getattr(Film.details, "related")

class A(DjangoObjectType):
class Meta:
model = FilmDetails

graphene_field = convert_django_field(related, A._meta.registry)
graphene_field = convert_django_field(Film.details.related,
A._meta.registry)
assert isinstance(graphene_field, graphene.Dynamic)
dynamic_field = graphene_field.get_type()
assert isinstance(dynamic_field, graphene.Field)
Expand Down
7 changes: 4 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"coveralls",
"mock",
"pytz",
"django-filter<2",
"django-filter<2;python_version<'3'",
"django-filter>=2;python_version>='3'",
"pytest-django>=3.3.2",
] + rest_framework_require

Expand All @@ -39,9 +40,9 @@
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: Implementation :: PyPy",
],
keywords="api graphql protocol rest relay graphene",
Expand All @@ -50,7 +51,7 @@
"six>=1.10.0",
"graphene>=2.1,<3",
"graphql-core>=2.1rc1",
"Django>=1.8.0",
"Django>=1.11",
"iso8601",
"singledispatch>=3.4.0.3",
"promise>=2.1",
Expand Down

0 comments on commit e45708b

Please sign in to comment.