Skip to content

Commit

Permalink
Merge pull request #65 from graphql-python/features/django-filter-1-0
Browse files Browse the repository at this point in the history
Updated Django Filter integration to 1.0
  • Loading branch information
syrusakbary authored Nov 23, 2016
2 parents 48993dd + 9d35b76 commit 37f8464
Show file tree
Hide file tree
Showing 11 changed files with 46 additions and 132 deletions.
13 changes: 6 additions & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ before_install:
install:
- |
if [ "$TEST_TYPE" = build ]; then
pip install pytest==3.0.2 pytest-cov pytest-benchmark coveralls six pytest-django==2.9.1 mock django-filter
pip install django==$DJANGO_VERSION
pip install pytest==3.0.2 pytest-cov pytest-benchmark coveralls six pytest-django==2.9.1 mock
pip install django==$DJANGO_FILTER_VERSION
pip install -e .
python setup.py develop
elif [ "$TEST_TYPE" = lint ]; then
Expand All @@ -46,18 +45,18 @@ after_success:
fi
env:
matrix:
- TEST_TYPE=build DJANGO_VERSION=1.10 DJANGO_FILTER_VERSION=1.0.0
- TEST_TYPE=build DJANGO_VERSION=1.10
matrix:
fast_finish: true
include:
- python: '2.7'
env: TEST_TYPE=build DJANGO_VERSION=1.6 DJANGO_FILTER_VERSION=0.15.3
env: TEST_TYPE=build DJANGO_VERSION=1.6
- python: '2.7'
env: TEST_TYPE=build DJANGO_VERSION=1.7 DJANGO_FILTER_VERSION=0.15.3
env: TEST_TYPE=build DJANGO_VERSION=1.7
- python: '2.7'
env: TEST_TYPE=build DJANGO_VERSION=1.8 DJANGO_FILTER_VERSION=0.15.3
env: TEST_TYPE=build DJANGO_VERSION=1.8
- python: '2.7'
env: TEST_TYPE=build DJANGO_VERSION=1.9 DJANGO_FILTER_VERSION=1.0.0
env: TEST_TYPE=build DJANGO_VERSION=1.9
- python: '2.7'
env: TEST_TYPE=lint
deploy:
Expand Down
39 changes: 1 addition & 38 deletions docs/filtering.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,50 +91,13 @@ Which you could query as follows:
}
}
Orderable fields
----------------

Ordering can also be specified using ``filter_order_by``. Like
``filter_fields``, this value is also passed directly to
``django-filter`` as the ``order_by`` field. For full details see the
`order\_by
documentation <https://django-filter.readthedocs.org/en/latest/usage.html#ordering-using-order-by>`__.

For example:

.. code:: python
class AnimalNode(DjangoObjectType):
class Meta:
model = Animal
filter_fields = ['name', 'genus', 'is_domesticated']
# Either a tuple/list of fields upon which ordering is allowed, or
# True to allow filtering on all fields specified in filter_fields
filter_order_by = True
interfaces = (relay.Node, )
You can then control the ordering via the ``orderBy`` argument:

.. code::
query {
allAnimals(orderBy: "name") {
edges {
node {
id,
name
}
}
}
}
Custom Filtersets
-----------------

By default Graphene provides easy access to the most commonly used
features of ``django-filter``. This is done by transparently creating a
``django_filters.FilterSet`` class for you and passing in the values for
``filter_fields`` and ``filter_order_by``.
``filter_fields``.

However, you may find this to be insufficient. In these cases you can
create your own ``Filterset`` as follows:
Expand Down
2 changes: 0 additions & 2 deletions docs/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ Create ``cookbook/ingredients/schema.py`` and type the following:
class Meta:
model = Category
filter_fields = ['name', 'ingredients']
filter_order_by = ['name']
interfaces = (relay.Node, )
Expand All @@ -112,7 +111,6 @@ Create ``cookbook/ingredients/schema.py`` and type the following:
'category': ['exact'],
'category__name': ['exact'],
}
filter_order_by = ['name', 'category__name']
interfaces = (relay.Node, )
Expand Down
4 changes: 2 additions & 2 deletions graphene_django/filter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
)
else:
from .fields import DjangoFilterConnectionField
from .filterset import GrapheneFilterSet, GlobalIDFilter, GlobalIDMultipleChoiceFilter
from .filterset import GlobalIDFilter, GlobalIDMultipleChoiceFilter

__all__ = ['DjangoFilterConnectionField', 'GrapheneFilterSet',
__all__ = ['DjangoFilterConnectionField',
'GlobalIDFilter', 'GlobalIDMultipleChoiceFilter']
15 changes: 4 additions & 11 deletions graphene_django/filter/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,12 @@

class DjangoFilterConnectionField(DjangoConnectionField):

def __init__(self, type, fields=None, order_by=None,
extra_filter_meta=None, filterset_class=None,
*args, **kwargs):
def __init__(self, type, fields=None, extra_filter_meta=None,
filterset_class=None, *args, **kwargs):

self.order_by = order_by or type._meta.filter_order_by
self.fields = fields or type._meta.filter_fields
meta = dict(model=type._meta.model,
fields=self.fields,
order_by=self.order_by)
fields=self.fields)
if extra_filter_meta:
meta.update(extra_filter_meta)
self.filterset_class = get_filterset_class(filterset_class, **meta)
Expand All @@ -27,12 +24,8 @@ def __init__(self, type, fields=None, order_by=None,
def connection_resolver(resolver, connection, default_manager, filterset_class, filtering_args,
root, args, context, info):
filter_kwargs = {k: v for k, v in args.items() if k in filtering_args}
order = args.get('order_by', None)
qs = default_manager.get_queryset()
if order:
qs = qs.order_by(order)
qs = filterset_class(data=filter_kwargs, queryset=qs)

qs = filterset_class(data=filter_kwargs, queryset=qs).qs
return DjangoConnectionField.connection_resolver(resolver, connection, qs, root, args, context, info)

def get_resolver(self, parent_resolver):
Expand Down
42 changes: 5 additions & 37 deletions graphene_django/filter/filterset.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import itertools

import six
from django.conf import settings
from django.db import models
from django.utils.text import capfirst
from django_filters import Filter, MultipleChoiceFilter
from django_filters.filterset import FilterSet, FilterSetMetaclass
from django_filters.filterset import BaseFilterSet, FilterSet
from django_filters.filterset import FILTER_FOR_DBFIELD_DEFAULTS

from graphql_relay.node.node import from_global_id
Expand All @@ -29,9 +27,6 @@ def filter(self, qs, value):
return super(GlobalIDMultipleChoiceFilter, self).filter(qs, gids)


ORDER_BY_FIELD = getattr(settings, 'GRAPHENE_ORDER_BY_FIELD', 'order_by')


GRAPHENE_FILTER_SET_OVERRIDES = {
models.AutoField: {
'filter_class': GlobalIDFilter,
Expand All @@ -48,25 +43,7 @@ def filter(self, qs, value):
}


# Only useful for Django-filter 0.14-, not necessary in latest version 0.15+
class GrapheneFilterSetMetaclass(FilterSetMetaclass):

def __new__(cls, name, bases, attrs):
new_class = super(GrapheneFilterSetMetaclass, cls).__new__(cls, name, bases, attrs)
# Customise the filter_overrides for Graphene
if hasattr(new_class, '_meta') and hasattr(new_class._meta, 'filter_overrides'):
filter_overrides = new_class._meta.filter_overrides
else:
filter_overrides = new_class.filter_overrides

for k, v in GRAPHENE_FILTER_SET_OVERRIDES.items():
filter_overrides.setdefault(k, v)

return new_class


class GrapheneFilterSetMixin(object):
order_by_field = ORDER_BY_FIELD
class GrapheneFilterSetMixin(BaseFilterSet):
FILTER_DEFAULTS = dict(itertools.chain(
FILTER_FOR_DBFIELD_DEFAULTS.items(),
GRAPHENE_FILTER_SET_OVERRIDES.items()
Expand All @@ -93,26 +70,17 @@ def filter_for_reverse_field(cls, f, name):
return GlobalIDFilter(**default)


class GrapheneFilterSet(six.with_metaclass(GrapheneFilterSetMetaclass, GrapheneFilterSetMixin, FilterSet)):
""" Base class for FilterSets used by Graphene
You shouldn't usually need to use this class. The
DjangoFilterConnectionField will wrap FilterSets with this class as
necessary
"""


def setup_filterset(filterset_class):
""" Wrap a provided filterset in Graphene-specific functionality
"""
return type(
'Graphene{}'.format(filterset_class.__name__),
(six.with_metaclass(GrapheneFilterSetMetaclass, GrapheneFilterSetMixin, filterset_class),),
(filterset_class, GrapheneFilterSetMixin),
{},
)


def custom_filterset_factory(model, filterset_base_class=GrapheneFilterSet,
def custom_filterset_factory(model, filterset_base_class=FilterSet,
**meta):
""" Create a filterset for the given model using the provided meta data
"""
Expand All @@ -122,7 +90,7 @@ def custom_filterset_factory(model, filterset_base_class=GrapheneFilterSet,
meta_class = type(str('Meta'), (object,), meta)
filterset = type(
str('%sFilterSet' % model._meta.object_name),
(filterset_base_class,),
(filterset_base_class, GrapheneFilterSetMixin),
{
'Meta': meta_class
}
Expand Down
8 changes: 5 additions & 3 deletions graphene_django/filter/tests/filters.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import django_filters
from django_filters import OrderingFilter

from graphene_django.tests.models import Article, Pet, Reporter

Expand All @@ -12,20 +13,21 @@ class Meta:
'pub_date': ['gt', 'lt', 'exact'],
'reporter': ['exact'],
}
order_by = False

order_by = OrderingFilter(fields=('pub_date',))


class ReporterFilter(django_filters.FilterSet):

class Meta:
model = Reporter
fields = ['first_name', 'last_name', 'email', 'pets']
order_by = True

order_by = OrderingFilter(fields=('pub_date',))


class PetFilter(django_filters.FilterSet):

class Meta:
model = Pet
fields = ['name']
order_by = False
46 changes: 22 additions & 24 deletions graphene_django/filter/tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from graphene_django.utils import DJANGO_FILTER_INSTALLED

pytestmark = []

if DJANGO_FILTER_INSTALLED:
import django_filters
from graphene_django.filter import (GlobalIDFilter, DjangoFilterConnectionField,
Expand All @@ -22,27 +23,29 @@
pytestmark.append(pytest.mark.django_db)


class ArticleNode(DjangoObjectType):
if DJANGO_FILTER_INSTALLED:
class ArticleNode(DjangoObjectType):

class Meta:
model = Article
interfaces = (Node, )
class Meta:
model = Article
interfaces = (Node, )
filter_fields = ('headline', )


class ReporterNode(DjangoObjectType):
class ReporterNode(DjangoObjectType):

class Meta:
model = Reporter
interfaces = (Node, )
class Meta:
model = Reporter
interfaces = (Node, )


class PetNode(DjangoObjectType):
class PetNode(DjangoObjectType):

class Meta:
model = Pet
interfaces = (Node, )
class Meta:
model = Pet
interfaces = (Node, )

# schema = Schema()
# schema = Schema()


def get_args(field):
Expand Down Expand Up @@ -110,8 +113,8 @@ def test_filter_explicit_filterset_orderable():


def test_filter_shortcut_filterset_orderable_true():
field = DjangoFilterConnectionField(ReporterNode, order_by=True)
assert_orderable(field)
field = DjangoFilterConnectionField(ReporterNode)
assert_not_orderable(field)


# def test_filter_shortcut_filterset_orderable_headline():
Expand All @@ -126,9 +129,9 @@ def test_filter_explicit_filterset_not_orderable():

def test_filter_shortcut_filterset_extra_meta():
field = DjangoFilterConnectionField(ArticleNode, extra_filter_meta={
'order_by': True
'exclude': ('headline', )
})
assert_orderable(field)
assert 'headline' not in field.filterset_class.get_fields()


def test_filter_filterset_information_on_meta():
Expand All @@ -138,11 +141,10 @@ class Meta:
model = Reporter
interfaces = (Node, )
filter_fields = ['first_name', 'articles']
filter_order_by = True

field = DjangoFilterConnectionField(ReporterFilterNode)
assert_arguments(field, 'first_name', 'articles')
assert_orderable(field)
assert_not_orderable(field)


def test_filter_filterset_information_on_meta_related():
Expand All @@ -152,15 +154,13 @@ class Meta:
model = Reporter
interfaces = (Node, )
filter_fields = ['first_name', 'articles']
filter_order_by = True

class ArticleFilterNode(DjangoObjectType):

class Meta:
model = Article
interfaces = (Node, )
filter_fields = ['headline', 'reporter']
filter_order_by = True

class Query(ObjectType):
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
Expand All @@ -171,7 +171,7 @@ class Query(ObjectType):
schema = Schema(query=Query)
articles_field = ReporterFilterNode._meta.fields['articles'].get_type()
assert_arguments(articles_field, 'headline', 'reporter')
assert_orderable(articles_field)
assert_not_orderable(articles_field)


def test_filter_filterset_related_results():
Expand All @@ -181,15 +181,13 @@ class Meta:
model = Reporter
interfaces = (Node, )
filter_fields = ['first_name', 'articles']
filter_order_by = True

class ArticleFilterNode(DjangoObjectType):

class Meta:
interfaces = (Node, )
model = Article
filter_fields = ['headline', 'reporter']
filter_order_by = True

class Query(ObjectType):
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
Expand Down
Loading

0 comments on commit 37f8464

Please sign in to comment.