Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Paul Bailey 2016-11-28 12:55:20 -05:00
commit aa6edfc62c
11 changed files with 42 additions and 127 deletions

View File

@ -45,7 +45,7 @@ after_success:
fi fi
env: env:
matrix: matrix:
- TEST_TYPE=build - TEST_TYPE=build DJANGO_VERSION=1.10
matrix: matrix:
fast_finish: true fast_finish: true
include: include:

View File

@ -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 Custom Filtersets
----------------- -----------------
By default Graphene provides easy access to the most commonly used By default Graphene provides easy access to the most commonly used
features of ``django-filter``. This is done by transparently creating a features of ``django-filter``. This is done by transparently creating a
``django_filters.FilterSet`` class for you and passing in the values for ``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 However, you may find this to be insufficient. In these cases you can
create your own ``Filterset`` as follows: create your own ``Filterset`` as follows:

View File

@ -98,7 +98,6 @@ Create ``cookbook/ingredients/schema.py`` and type the following:
class Meta: class Meta:
model = Category model = Category
filter_fields = ['name', 'ingredients'] filter_fields = ['name', 'ingredients']
filter_order_by = ['name']
interfaces = (relay.Node, ) interfaces = (relay.Node, )
@ -112,7 +111,6 @@ Create ``cookbook/ingredients/schema.py`` and type the following:
'category': ['exact'], 'category': ['exact'],
'category__name': ['exact'], 'category__name': ['exact'],
} }
filter_order_by = ['name', 'category__name']
interfaces = (relay.Node, ) interfaces = (relay.Node, )

View File

@ -8,7 +8,7 @@ if not DJANGO_FILTER_INSTALLED:
) )
else: else:
from .fields import DjangoFilterConnectionField from .fields import DjangoFilterConnectionField
from .filterset import GrapheneFilterSet, GlobalIDFilter, GlobalIDMultipleChoiceFilter from .filterset import GlobalIDFilter, GlobalIDMultipleChoiceFilter
__all__ = ['DjangoFilterConnectionField', 'GrapheneFilterSet', __all__ = ['DjangoFilterConnectionField',
'GlobalIDFilter', 'GlobalIDMultipleChoiceFilter'] 'GlobalIDFilter', 'GlobalIDMultipleChoiceFilter']

View File

@ -6,15 +6,12 @@ from .utils import get_filtering_args_from_filterset, get_filterset_class
class DjangoFilterConnectionField(DjangoConnectionField): class DjangoFilterConnectionField(DjangoConnectionField):
def __init__(self, type, fields=None, order_by=None, def __init__(self, type, fields=None, extra_filter_meta=None,
extra_filter_meta=None, filterset_class=None, filterset_class=None, *args, **kwargs):
*args, **kwargs):
self.order_by = order_by or type._meta.filter_order_by
self.fields = fields or type._meta.filter_fields self.fields = fields or type._meta.filter_fields
meta = dict(model=type._meta.model, meta = dict(model=type._meta.model,
fields=self.fields, fields=self.fields)
order_by=self.order_by)
if extra_filter_meta: if extra_filter_meta:
meta.update(extra_filter_meta) meta.update(extra_filter_meta)
self.filterset_class = get_filterset_class(filterset_class, **meta) self.filterset_class = get_filterset_class(filterset_class, **meta)
@ -27,12 +24,8 @@ class DjangoFilterConnectionField(DjangoConnectionField):
def connection_resolver(resolver, connection, default_manager, filterset_class, filtering_args, def connection_resolver(resolver, connection, default_manager, filterset_class, filtering_args,
root, args, context, info): root, args, context, info):
filter_kwargs = {k: v for k, v in args.items() if k in filtering_args} 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() qs = default_manager.get_queryset()
if order: qs = filterset_class(data=filter_kwargs, queryset=qs).qs
qs = qs.order_by(order)
qs = filterset_class(data=filter_kwargs, queryset=qs)
return DjangoConnectionField.connection_resolver(resolver, connection, qs, root, args, context, info) return DjangoConnectionField.connection_resolver(resolver, connection, qs, root, args, context, info)
def get_resolver(self, parent_resolver): def get_resolver(self, parent_resolver):

View File

@ -1,11 +1,9 @@
import itertools import itertools
import six
from django.conf import settings
from django.db import models from django.db import models
from django.utils.text import capfirst from django.utils.text import capfirst
from django_filters import Filter, MultipleChoiceFilter 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 django_filters.filterset import FILTER_FOR_DBFIELD_DEFAULTS
from graphql_relay.node.node import from_global_id from graphql_relay.node.node import from_global_id
@ -29,9 +27,6 @@ class GlobalIDMultipleChoiceFilter(MultipleChoiceFilter):
return super(GlobalIDMultipleChoiceFilter, self).filter(qs, gids) return super(GlobalIDMultipleChoiceFilter, self).filter(qs, gids)
ORDER_BY_FIELD = getattr(settings, 'GRAPHENE_ORDER_BY_FIELD', 'order_by')
GRAPHENE_FILTER_SET_OVERRIDES = { GRAPHENE_FILTER_SET_OVERRIDES = {
models.AutoField: { models.AutoField: {
'filter_class': GlobalIDFilter, 'filter_class': GlobalIDFilter,
@ -48,25 +43,7 @@ GRAPHENE_FILTER_SET_OVERRIDES = {
} }
# Only useful for Django-filter 0.14-, not necessary in latest version 0.15+ class GrapheneFilterSetMixin(BaseFilterSet):
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
FILTER_DEFAULTS = dict(itertools.chain( FILTER_DEFAULTS = dict(itertools.chain(
FILTER_FOR_DBFIELD_DEFAULTS.items(), FILTER_FOR_DBFIELD_DEFAULTS.items(),
GRAPHENE_FILTER_SET_OVERRIDES.items() GRAPHENE_FILTER_SET_OVERRIDES.items()
@ -93,26 +70,17 @@ class GrapheneFilterSetMixin(object):
return GlobalIDFilter(**default) 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): def setup_filterset(filterset_class):
""" Wrap a provided filterset in Graphene-specific functionality """ Wrap a provided filterset in Graphene-specific functionality
""" """
return type( return type(
'Graphene{}'.format(filterset_class.__name__), '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): **meta):
""" Create a filterset for the given model using the provided meta data """ Create a filterset for the given model using the provided meta data
""" """
@ -122,7 +90,7 @@ def custom_filterset_factory(model, filterset_base_class=GrapheneFilterSet,
meta_class = type(str('Meta'), (object,), meta) meta_class = type(str('Meta'), (object,), meta)
filterset = type( filterset = type(
str('%sFilterSet' % model._meta.object_name), str('%sFilterSet' % model._meta.object_name),
(filterset_base_class,), (filterset_base_class, GrapheneFilterSetMixin),
{ {
'Meta': meta_class 'Meta': meta_class
} }

View File

@ -1,4 +1,5 @@
import django_filters import django_filters
from django_filters import OrderingFilter
from graphene_django.tests.models import Article, Pet, Reporter from graphene_django.tests.models import Article, Pet, Reporter
@ -12,7 +13,8 @@ class ArticleFilter(django_filters.FilterSet):
'pub_date': ['gt', 'lt', 'exact'], 'pub_date': ['gt', 'lt', 'exact'],
'reporter': ['exact'], 'reporter': ['exact'],
} }
order_by = False
order_by = OrderingFilter(fields=('pub_date',))
class ReporterFilter(django_filters.FilterSet): class ReporterFilter(django_filters.FilterSet):
@ -20,7 +22,8 @@ class ReporterFilter(django_filters.FilterSet):
class Meta: class Meta:
model = Reporter model = Reporter
fields = ['first_name', 'last_name', 'email', 'pets'] fields = ['first_name', 'last_name', 'email', 'pets']
order_by = True
order_by = OrderingFilter(fields=('pub_date',))
class PetFilter(django_filters.FilterSet): class PetFilter(django_filters.FilterSet):
@ -28,4 +31,3 @@ class PetFilter(django_filters.FilterSet):
class Meta: class Meta:
model = Pet model = Pet
fields = ['name'] fields = ['name']
order_by = False

View File

@ -11,6 +11,7 @@ from graphene_django.tests.models import Article, Pet, Reporter
from graphene_django.utils import DJANGO_FILTER_INSTALLED from graphene_django.utils import DJANGO_FILTER_INSTALLED
pytestmark = [] pytestmark = []
if DJANGO_FILTER_INSTALLED: if DJANGO_FILTER_INSTALLED:
import django_filters import django_filters
from graphene_django.filter import (GlobalIDFilter, DjangoFilterConnectionField, from graphene_django.filter import (GlobalIDFilter, DjangoFilterConnectionField,
@ -22,27 +23,29 @@ else:
pytestmark.append(pytest.mark.django_db) pytestmark.append(pytest.mark.django_db)
class ArticleNode(DjangoObjectType): if DJANGO_FILTER_INSTALLED:
class ArticleNode(DjangoObjectType):
class Meta: class Meta:
model = Article model = Article
interfaces = (Node, ) interfaces = (Node, )
filter_fields = ('headline', )
class ReporterNode(DjangoObjectType): class ReporterNode(DjangoObjectType):
class Meta: class Meta:
model = Reporter model = Reporter
interfaces = (Node, ) interfaces = (Node, )
class PetNode(DjangoObjectType): class PetNode(DjangoObjectType):
class Meta: class Meta:
model = Pet model = Pet
interfaces = (Node, ) interfaces = (Node, )
# schema = Schema() # schema = Schema()
def get_args(field): def get_args(field):
@ -110,8 +113,8 @@ def test_filter_explicit_filterset_orderable():
def test_filter_shortcut_filterset_orderable_true(): def test_filter_shortcut_filterset_orderable_true():
field = DjangoFilterConnectionField(ReporterNode, order_by=True) field = DjangoFilterConnectionField(ReporterNode)
assert_orderable(field) assert_not_orderable(field)
# def test_filter_shortcut_filterset_orderable_headline(): # def test_filter_shortcut_filterset_orderable_headline():
@ -126,9 +129,9 @@ def test_filter_explicit_filterset_not_orderable():
def test_filter_shortcut_filterset_extra_meta(): def test_filter_shortcut_filterset_extra_meta():
field = DjangoFilterConnectionField(ArticleNode, extra_filter_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(): def test_filter_filterset_information_on_meta():
@ -138,11 +141,10 @@ def test_filter_filterset_information_on_meta():
model = Reporter model = Reporter
interfaces = (Node, ) interfaces = (Node, )
filter_fields = ['first_name', 'articles'] filter_fields = ['first_name', 'articles']
filter_order_by = True
field = DjangoFilterConnectionField(ReporterFilterNode) field = DjangoFilterConnectionField(ReporterFilterNode)
assert_arguments(field, 'first_name', 'articles') assert_arguments(field, 'first_name', 'articles')
assert_orderable(field) assert_not_orderable(field)
def test_filter_filterset_information_on_meta_related(): def test_filter_filterset_information_on_meta_related():
@ -152,7 +154,6 @@ def test_filter_filterset_information_on_meta_related():
model = Reporter model = Reporter
interfaces = (Node, ) interfaces = (Node, )
filter_fields = ['first_name', 'articles'] filter_fields = ['first_name', 'articles']
filter_order_by = True
class ArticleFilterNode(DjangoObjectType): class ArticleFilterNode(DjangoObjectType):
@ -160,7 +161,6 @@ def test_filter_filterset_information_on_meta_related():
model = Article model = Article
interfaces = (Node, ) interfaces = (Node, )
filter_fields = ['headline', 'reporter'] filter_fields = ['headline', 'reporter']
filter_order_by = True
class Query(ObjectType): class Query(ObjectType):
all_reporters = DjangoFilterConnectionField(ReporterFilterNode) all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
@ -171,7 +171,7 @@ def test_filter_filterset_information_on_meta_related():
schema = Schema(query=Query) schema = Schema(query=Query)
articles_field = ReporterFilterNode._meta.fields['articles'].get_type() articles_field = ReporterFilterNode._meta.fields['articles'].get_type()
assert_arguments(articles_field, 'headline', 'reporter') assert_arguments(articles_field, 'headline', 'reporter')
assert_orderable(articles_field) assert_not_orderable(articles_field)
def test_filter_filterset_related_results(): def test_filter_filterset_related_results():
@ -181,7 +181,6 @@ def test_filter_filterset_related_results():
model = Reporter model = Reporter
interfaces = (Node, ) interfaces = (Node, )
filter_fields = ['first_name', 'articles'] filter_fields = ['first_name', 'articles']
filter_order_by = True
class ArticleFilterNode(DjangoObjectType): class ArticleFilterNode(DjangoObjectType):
@ -189,7 +188,6 @@ def test_filter_filterset_related_results():
interfaces = (Node, ) interfaces = (Node, )
model = Article model = Article
filter_fields = ['headline', 'reporter'] filter_fields = ['headline', 'reporter']
filter_order_by = True
class Query(ObjectType): class Query(ObjectType):
all_reporters = DjangoFilterConnectionField(ReporterFilterNode) all_reporters = DjangoFilterConnectionField(ReporterFilterNode)

View File

@ -1,7 +1,5 @@
import six import six
from graphene import String
from .filterset import custom_filterset_factory, setup_filterset from .filterset import custom_filterset_factory, setup_filterset
@ -18,10 +16,6 @@ def get_filtering_args_from_filterset(filterset_class, type):
field_type.description = filter_field.label field_type.description = filter_field.label
args[name] = field_type args[name] = field_type
# Also add the 'order_by' field
if filterset_class._meta.order_by:
args[filterset_class.order_by_field] = String()
return args return args

View File

@ -65,7 +65,6 @@ class DjangoObjectTypeMeta(ObjectTypeMeta):
# we allow more attributes in Meta # we allow more attributes in Meta
defaults.update( defaults.update(
filter_fields=(), filter_fields=(),
filter_order_by=(),
) )
options = Options( options = Options(

View File

@ -2,7 +2,7 @@ from setuptools import find_packages, setup
setup( setup(
name='graphene-django', name='graphene-django',
version='1.1.0', version='1.2.0',
description='Graphene Django integration', description='Graphene Django integration',
long_description=open('README.rst').read(), long_description=open('README.rst').read(),
@ -42,7 +42,7 @@ setup(
'pytest-runner', 'pytest-runner',
], ],
tests_require=[ tests_require=[
'django-filter>=0.10.0', 'django-filter>=1.0.0',
'pytest', 'pytest',
'pytest-django==2.9.1', 'pytest-django==2.9.1',
'mock', 'mock',