mirror of
https://github.com/graphql-python/graphene-django.git
synced 2024-11-25 19:14:11 +03:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
aa6edfc62c
|
@ -45,7 +45,7 @@ after_success:
|
|||
fi
|
||||
env:
|
||||
matrix:
|
||||
- TEST_TYPE=build
|
||||
- TEST_TYPE=build DJANGO_VERSION=1.10
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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, )
|
||||
|
||||
|
||||
|
@ -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, )
|
||||
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ if not DJANGO_FILTER_INSTALLED:
|
|||
)
|
||||
else:
|
||||
from .fields import DjangoFilterConnectionField
|
||||
from .filterset import GrapheneFilterSet, GlobalIDFilter, GlobalIDMultipleChoiceFilter
|
||||
from .filterset import GlobalIDFilter, GlobalIDMultipleChoiceFilter
|
||||
|
||||
__all__ = ['DjangoFilterConnectionField', 'GrapheneFilterSet',
|
||||
__all__ = ['DjangoFilterConnectionField',
|
||||
'GlobalIDFilter', 'GlobalIDMultipleChoiceFilter']
|
||||
|
|
|
@ -6,15 +6,12 @@ from .utils import get_filtering_args_from_filterset, get_filterset_class
|
|||
|
||||
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)
|
||||
|
@ -27,12 +24,8 @@ class DjangoFilterConnectionField(DjangoConnectionField):
|
|||
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):
|
||||
|
|
|
@ -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
|
||||
|
@ -29,9 +27,6 @@ class GlobalIDMultipleChoiceFilter(MultipleChoiceFilter):
|
|||
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,
|
||||
|
@ -48,25 +43,7 @@ GRAPHENE_FILTER_SET_OVERRIDES = {
|
|||
}
|
||||
|
||||
|
||||
# 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()
|
||||
|
@ -93,26 +70,17 @@ class GrapheneFilterSetMixin(object):
|
|||
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
|
||||
"""
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import django_filters
|
||||
from django_filters import OrderingFilter
|
||||
|
||||
from graphene_django.tests.models import Article, Pet, Reporter
|
||||
|
||||
|
@ -12,7 +13,8 @@ class ArticleFilter(django_filters.FilterSet):
|
|||
'pub_date': ['gt', 'lt', 'exact'],
|
||||
'reporter': ['exact'],
|
||||
}
|
||||
order_by = False
|
||||
|
||||
order_by = OrderingFilter(fields=('pub_date',))
|
||||
|
||||
|
||||
class ReporterFilter(django_filters.FilterSet):
|
||||
|
@ -20,7 +22,8 @@ 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):
|
||||
|
@ -28,4 +31,3 @@ class PetFilter(django_filters.FilterSet):
|
|||
class Meta:
|
||||
model = Pet
|
||||
fields = ['name']
|
||||
order_by = False
|
||||
|
|
|
@ -11,6 +11,7 @@ from graphene_django.tests.models import Article, Pet, Reporter
|
|||
from graphene_django.utils import DJANGO_FILTER_INSTALLED
|
||||
|
||||
pytestmark = []
|
||||
|
||||
if DJANGO_FILTER_INSTALLED:
|
||||
import django_filters
|
||||
from graphene_django.filter import (GlobalIDFilter, DjangoFilterConnectionField,
|
||||
|
@ -22,27 +23,29 @@ else:
|
|||
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):
|
||||
|
@ -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():
|
||||
|
@ -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():
|
||||
|
@ -138,11 +141,10 @@ def test_filter_filterset_information_on_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():
|
||||
|
@ -152,7 +154,6 @@ def test_filter_filterset_information_on_meta_related():
|
|||
model = Reporter
|
||||
interfaces = (Node, )
|
||||
filter_fields = ['first_name', 'articles']
|
||||
filter_order_by = True
|
||||
|
||||
class ArticleFilterNode(DjangoObjectType):
|
||||
|
||||
|
@ -160,7 +161,6 @@ def test_filter_filterset_information_on_meta_related():
|
|||
model = Article
|
||||
interfaces = (Node, )
|
||||
filter_fields = ['headline', 'reporter']
|
||||
filter_order_by = True
|
||||
|
||||
class Query(ObjectType):
|
||||
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
|
||||
|
@ -171,7 +171,7 @@ def test_filter_filterset_information_on_meta_related():
|
|||
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():
|
||||
|
@ -181,7 +181,6 @@ def test_filter_filterset_related_results():
|
|||
model = Reporter
|
||||
interfaces = (Node, )
|
||||
filter_fields = ['first_name', 'articles']
|
||||
filter_order_by = True
|
||||
|
||||
class ArticleFilterNode(DjangoObjectType):
|
||||
|
||||
|
@ -189,7 +188,6 @@ def test_filter_filterset_related_results():
|
|||
interfaces = (Node, )
|
||||
model = Article
|
||||
filter_fields = ['headline', 'reporter']
|
||||
filter_order_by = True
|
||||
|
||||
class Query(ObjectType):
|
||||
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import six
|
||||
|
||||
from graphene import String
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
|
|
|
@ -65,7 +65,6 @@ class DjangoObjectTypeMeta(ObjectTypeMeta):
|
|||
# we allow more attributes in Meta
|
||||
defaults.update(
|
||||
filter_fields=(),
|
||||
filter_order_by=(),
|
||||
)
|
||||
|
||||
options = Options(
|
||||
|
|
4
setup.py
4
setup.py
|
@ -2,7 +2,7 @@ from setuptools import find_packages, setup
|
|||
|
||||
setup(
|
||||
name='graphene-django',
|
||||
version='1.1.0',
|
||||
version='1.2.0',
|
||||
|
||||
description='Graphene Django integration',
|
||||
long_description=open('README.rst').read(),
|
||||
|
@ -42,7 +42,7 @@ setup(
|
|||
'pytest-runner',
|
||||
],
|
||||
tests_require=[
|
||||
'django-filter>=0.10.0',
|
||||
'django-filter>=1.0.0',
|
||||
'pytest',
|
||||
'pytest-django==2.9.1',
|
||||
'mock',
|
||||
|
|
Loading…
Reference in New Issue
Block a user