Skip to content

Make GrapheneFilterSetMixin compatible with django-filter 2 #492

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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