mirror of
https://github.com/graphql-python/graphene.git
synced 2025-02-08 23:50:38 +03:00
Moving django-filter dependent code into graphene.contrib.django.filter
Graphene should now run fine without django-filter. Tests will also run without django-filter. However, I'm leaving it as a requirement in setup.py's `tests_require` setting as testing without it is probably not to be encouraged.
This commit is contained in:
parent
3709f9450b
commit
fb45a83925
|
@ -5,10 +5,8 @@ from graphene.contrib.django.types import (
|
||||||
)
|
)
|
||||||
from graphene.contrib.django.fields import (
|
from graphene.contrib.django.fields import (
|
||||||
DjangoConnectionField,
|
DjangoConnectionField,
|
||||||
DjangoModelField,
|
DjangoModelField
|
||||||
DjangoFilterConnectionField
|
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = ['DjangoObjectType', 'DjangoNode', 'DjangoConnection',
|
__all__ = ['DjangoObjectType', 'DjangoNode', 'DjangoConnection',
|
||||||
'DjangoConnectionField', 'DjangoModelField',
|
'DjangoModelField']
|
||||||
'DjangoFilterConnectionField']
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from graphene.contrib.django.utils import get_filtering_args_from_filterset
|
|
||||||
from .resolvers import FilterConnectionResolver
|
|
||||||
from .utils import get_type_for_model
|
from .utils import get_type_for_model
|
||||||
from ...core.exceptions import SkipField
|
from ...core.exceptions import SkipField
|
||||||
from ...core.fields import Field
|
from ...core.fields import Field
|
||||||
|
@ -62,23 +60,3 @@ class DjangoModelField(FieldType):
|
||||||
return get_type_for_model(schema, self.model)
|
return get_type_for_model(schema, self.model)
|
||||||
|
|
||||||
|
|
||||||
class DjangoFilterConnectionField(DjangoConnectionField):
|
|
||||||
|
|
||||||
def __init__(self, type, on=None, fields=None, order_by=None,
|
|
||||||
extra_filter_meta=None, filterset_class=None, resolver=None,
|
|
||||||
*args, **kwargs):
|
|
||||||
|
|
||||||
if not resolver:
|
|
||||||
resolver = FilterConnectionResolver(
|
|
||||||
node=type,
|
|
||||||
on=on,
|
|
||||||
filterset_class=filterset_class,
|
|
||||||
fields=fields,
|
|
||||||
order_by=order_by,
|
|
||||||
extra_filter_meta=extra_filter_meta,
|
|
||||||
)
|
|
||||||
|
|
||||||
filtering_args = get_filtering_args_from_filterset(resolver.get_filterset_class(), type)
|
|
||||||
kwargs.setdefault('args', {})
|
|
||||||
kwargs['args'].update(**filtering_args)
|
|
||||||
super(DjangoFilterConnectionField, self).__init__(type, resolver, *args, **kwargs)
|
|
||||||
|
|
3
graphene/contrib/django/filter/__init__.py
Normal file
3
graphene/contrib/django/filter/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from .fields import DjangoFilterConnectionField
|
||||||
|
from .filterset import GrapheneFilterSet, GlobalIDFilter
|
||||||
|
from .resolvers import FilterConnectionResolver
|
25
graphene/contrib/django/filter/fields.py
Normal file
25
graphene/contrib/django/filter/fields.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
from graphene.contrib.django import DjangoConnectionField
|
||||||
|
from graphene.contrib.django.filter.resolvers import FilterConnectionResolver
|
||||||
|
from graphene.contrib.django.utils import get_filtering_args_from_filterset
|
||||||
|
|
||||||
|
|
||||||
|
class DjangoFilterConnectionField(DjangoConnectionField):
|
||||||
|
|
||||||
|
def __init__(self, type, on=None, fields=None, order_by=None,
|
||||||
|
extra_filter_meta=None, filterset_class=None, resolver=None,
|
||||||
|
*args, **kwargs):
|
||||||
|
|
||||||
|
if not resolver:
|
||||||
|
resolver = FilterConnectionResolver(
|
||||||
|
node=type,
|
||||||
|
on=on,
|
||||||
|
filterset_class=filterset_class,
|
||||||
|
fields=fields,
|
||||||
|
order_by=order_by,
|
||||||
|
extra_filter_meta=extra_filter_meta,
|
||||||
|
)
|
||||||
|
|
||||||
|
filtering_args = get_filtering_args_from_filterset(resolver.get_filterset_class(), type)
|
||||||
|
kwargs.setdefault('args', {})
|
||||||
|
kwargs['args'].update(**filtering_args)
|
||||||
|
super(DjangoFilterConnectionField, self).__init__(type, resolver, *args, **kwargs)
|
63
graphene/contrib/django/filter/resolvers.py
Normal file
63
graphene/contrib/django/filter/resolvers.py
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
|
from graphene.contrib.django.filter.filterset import setup_filterset, custom_filterset_factory
|
||||||
|
from graphene.contrib.django.resolvers import BaseQuerySetConnectionResolver
|
||||||
|
|
||||||
|
|
||||||
|
class FilterConnectionResolver(BaseQuerySetConnectionResolver):
|
||||||
|
# Querying using django-filter
|
||||||
|
|
||||||
|
def __init__(self, node, on=None, filterset_class=None,
|
||||||
|
fields=None, order_by=None, extra_filter_meta=None):
|
||||||
|
self.filterset_class = filterset_class
|
||||||
|
self.fields = fields
|
||||||
|
self.order_by = order_by
|
||||||
|
self.extra_filter_meta = extra_filter_meta or {}
|
||||||
|
self._filterset_class = None
|
||||||
|
super(FilterConnectionResolver, self).__init__(node, on)
|
||||||
|
|
||||||
|
def make_query(self):
|
||||||
|
filterset_class = self.get_filterset_class()
|
||||||
|
filterset = self.get_filterset(filterset_class)
|
||||||
|
return filterset.qs
|
||||||
|
|
||||||
|
def get_filterset_class(self):
|
||||||
|
"""Get the class to be used as the FilterSet"""
|
||||||
|
if self._filterset_class:
|
||||||
|
return self._filterset_class
|
||||||
|
|
||||||
|
if self.filterset_class:
|
||||||
|
# If were given a FilterSet class, then set it up and
|
||||||
|
# return it
|
||||||
|
self._filterset_class = setup_filterset(self.filterset_class)
|
||||||
|
elif self.model:
|
||||||
|
# If no filter class was specified then create one given the
|
||||||
|
# other information provided
|
||||||
|
meta = dict(
|
||||||
|
model=self.model,
|
||||||
|
fields=self.fields,
|
||||||
|
order_by=self.order_by,
|
||||||
|
)
|
||||||
|
meta.update(self.extra_filter_meta)
|
||||||
|
self._filterset_class = custom_filterset_factory(**meta)
|
||||||
|
else:
|
||||||
|
msg = "Neither 'filterset_class' or 'model' available in '%s'. " \
|
||||||
|
"Either pass in 'filterset_class' or 'model' when " \
|
||||||
|
"initialising, or extend this class and override " \
|
||||||
|
"get_filterset() or get_filterset_class()"
|
||||||
|
raise ImproperlyConfigured(msg % self.__class__.__name__)
|
||||||
|
|
||||||
|
return self._filterset_class
|
||||||
|
|
||||||
|
def get_filterset(self, filterset_class):
|
||||||
|
"""Get an instance of the FilterSet"""
|
||||||
|
kwargs = self.get_filterset_kwargs(filterset_class)
|
||||||
|
return filterset_class(**kwargs)
|
||||||
|
|
||||||
|
def get_filterset_kwargs(self, filterset_class):
|
||||||
|
"""Get the kwargs to use when initialising the FilterSet class"""
|
||||||
|
kwargs = {
|
||||||
|
'data': self.args or None,
|
||||||
|
'queryset': self.get_manager()
|
||||||
|
}
|
||||||
|
return kwargs
|
|
@ -1,8 +1,3 @@
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
|
|
||||||
from graphene.contrib.django.filterset import setup_filterset, custom_filterset_factory
|
|
||||||
|
|
||||||
|
|
||||||
class BaseQuerySetConnectionResolver(object):
|
class BaseQuerySetConnectionResolver(object):
|
||||||
|
|
||||||
def __init__(self, node, on=None):
|
def __init__(self, node, on=None):
|
||||||
|
@ -48,60 +43,3 @@ class SimpleQuerySetConnectionResolver(BaseQuerySetConnectionResolver):
|
||||||
return self.args.get('order', None)
|
return self.args.get('order', None)
|
||||||
|
|
||||||
|
|
||||||
class FilterConnectionResolver(BaseQuerySetConnectionResolver):
|
|
||||||
# Querying using django-filter
|
|
||||||
|
|
||||||
def __init__(self, node, on=None, filterset_class=None,
|
|
||||||
fields=None, order_by=None, extra_filter_meta=None):
|
|
||||||
self.filterset_class = filterset_class
|
|
||||||
self.fields = fields
|
|
||||||
self.order_by = order_by
|
|
||||||
self.extra_filter_meta = extra_filter_meta or {}
|
|
||||||
self._filterset_class = None
|
|
||||||
super(FilterConnectionResolver, self).__init__(node, on)
|
|
||||||
|
|
||||||
def make_query(self):
|
|
||||||
filterset_class = self.get_filterset_class()
|
|
||||||
filterset = self.get_filterset(filterset_class)
|
|
||||||
return filterset.qs
|
|
||||||
|
|
||||||
def get_filterset_class(self):
|
|
||||||
"""Get the class to be used as the FilterSet"""
|
|
||||||
if self._filterset_class:
|
|
||||||
return self._filterset_class
|
|
||||||
|
|
||||||
if self.filterset_class:
|
|
||||||
# If were given a FilterSet class, then set it up and
|
|
||||||
# return it
|
|
||||||
self._filterset_class = setup_filterset(self.filterset_class)
|
|
||||||
elif self.model:
|
|
||||||
# If no filter class was specified then create one given the
|
|
||||||
# other information provided
|
|
||||||
meta = dict(
|
|
||||||
model=self.model,
|
|
||||||
fields=self.fields,
|
|
||||||
order_by=self.order_by,
|
|
||||||
)
|
|
||||||
meta.update(self.extra_filter_meta)
|
|
||||||
self._filterset_class = custom_filterset_factory(**meta)
|
|
||||||
else:
|
|
||||||
msg = "Neither 'filterset_class' or 'model' available in '%s'. " \
|
|
||||||
"Either pass in 'filterset_class' or 'model' when " \
|
|
||||||
"initialising, or extend this class and override " \
|
|
||||||
"get_filterset() or get_filterset_class()"
|
|
||||||
raise ImproperlyConfigured(msg % self.__class__.__name__)
|
|
||||||
|
|
||||||
return self._filterset_class
|
|
||||||
|
|
||||||
def get_filterset(self, filterset_class):
|
|
||||||
"""Get an instance of the FilterSet"""
|
|
||||||
kwargs = self.get_filterset_kwargs(filterset_class)
|
|
||||||
return filterset_class(**kwargs)
|
|
||||||
|
|
||||||
def get_filterset_kwargs(self, filterset_class):
|
|
||||||
"""Get the kwargs to use when initialising the FilterSet class"""
|
|
||||||
kwargs = {
|
|
||||||
'data': self.args or None,
|
|
||||||
'queryset': self.get_manager()
|
|
||||||
}
|
|
||||||
return kwargs
|
|
||||||
|
|
0
graphene/contrib/django/tests/filter/__init__.py
Normal file
0
graphene/contrib/django/tests/filter/__init__.py
Normal file
|
@ -1,7 +1,7 @@
|
||||||
import django_filters
|
import django_filters
|
||||||
|
|
||||||
from graphene.contrib.django.tests.models import Reporter
|
from graphene.contrib.django.tests.models import Reporter
|
||||||
from .models import Article, Pet
|
from graphene.contrib.django.tests.models import Article, Pet
|
||||||
|
|
||||||
|
|
||||||
class ArticleFilter(django_filters.FilterSet):
|
class ArticleFilter(django_filters.FilterSet):
|
|
@ -1,9 +1,15 @@
|
||||||
import django_filters
|
import pytest
|
||||||
|
|
||||||
from graphene.contrib.django import DjangoFilterConnectionField, DjangoNode
|
try:
|
||||||
from graphene.contrib.django.filterset import GlobalIDFilter
|
import django_filters
|
||||||
|
except ImportError:
|
||||||
|
pytestmark = pytest.mark.skipif(True, reason='django_filters not installed')
|
||||||
|
else:
|
||||||
|
from graphene.contrib.django.filter import GlobalIDFilter, DjangoFilterConnectionField
|
||||||
|
from graphene.contrib.django.tests.filter.filters import ArticleFilter, PetFilter
|
||||||
|
|
||||||
|
from graphene.contrib.django import DjangoNode
|
||||||
from graphene.contrib.django.forms import GlobalIDFormField
|
from graphene.contrib.django.forms import GlobalIDFormField
|
||||||
from graphene.contrib.django.tests.filters import ArticleFilter, PetFilter
|
|
||||||
from graphene.contrib.django.tests.models import Article, Pet
|
from graphene.contrib.django.tests.models import Article, Pet
|
||||||
|
|
||||||
|
|
84
graphene/contrib/django/tests/filter/test_resolvers.py
Normal file
84
graphene/contrib/django/tests/filter/test_resolvers.py
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import pytest
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
|
try:
|
||||||
|
import django_filters
|
||||||
|
except ImportError:
|
||||||
|
pytestmark = pytest.mark.skipif(True, reason='django_filters not installed')
|
||||||
|
else:
|
||||||
|
from graphene.contrib.django.filter.resolvers import FilterConnectionResolver
|
||||||
|
from graphene.contrib.django.tests.filter.filters import ReporterFilter, ArticleFilter
|
||||||
|
|
||||||
|
from graphene.contrib.django.tests.models import Reporter, Article
|
||||||
|
from graphene.contrib.django.tests.test_resolvers import ReporterNode, ArticleNode
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_get_filterset_class_explicit():
|
||||||
|
reporter = Reporter(id=1, first_name='Cookie Monster')
|
||||||
|
resolver = FilterConnectionResolver(ReporterNode,
|
||||||
|
filterset_class=ReporterFilter)
|
||||||
|
resolver(inst=reporter, args={}, info=None)
|
||||||
|
assert issubclass(resolver.get_filterset_class(), ReporterFilter), \
|
||||||
|
'ReporterFilter not returned'
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_get_filterset_class_implicit():
|
||||||
|
reporter = Reporter(id=1, first_name='Cookie Monster')
|
||||||
|
resolver = FilterConnectionResolver(ReporterNode)
|
||||||
|
resolver(inst=reporter, args={}, info=None)
|
||||||
|
assert resolver.get_filterset_class().__name__ == 'ReporterFilterSet'
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_get_filterset_class_error():
|
||||||
|
reporter = Reporter(id=1, first_name='Cookie Monster')
|
||||||
|
resolver = FilterConnectionResolver(ReporterNode)
|
||||||
|
resolver.model = None
|
||||||
|
with pytest.raises(ImproperlyConfigured) as excinfo:
|
||||||
|
resolver(inst=reporter, args={}, info=None)
|
||||||
|
assert "Neither 'filterset_class' or 'model' available" in str(excinfo.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_filter():
|
||||||
|
reporter = Reporter(id=1, first_name='Cookie Monster')
|
||||||
|
resolver = FilterConnectionResolver(ReporterNode,
|
||||||
|
filterset_class=ReporterFilter)
|
||||||
|
resolved = resolver(inst=reporter, args={
|
||||||
|
'first_name': 'Elmo'
|
||||||
|
}, info=None)
|
||||||
|
assert '"first_name" = Elmo' in str(resolved.query)
|
||||||
|
assert 'ORDER BY' not in str(resolved.query)
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_filter_contains():
|
||||||
|
article = Article(id=1, headline='Cookie Monster eats fruit')
|
||||||
|
resolver = FilterConnectionResolver(ArticleNode,
|
||||||
|
filterset_class=ArticleFilter)
|
||||||
|
resolved = resolver(inst=article, args={
|
||||||
|
'headline__icontains': 'Elmo'
|
||||||
|
}, info=None)
|
||||||
|
assert '"headline" LIKE %Elmo%' in str(resolved.query)
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_order():
|
||||||
|
article = Article(id=1, headline='Cookie Monster eats fruit')
|
||||||
|
resolver = FilterConnectionResolver(ArticleNode,
|
||||||
|
filterset_class=ArticleFilter)
|
||||||
|
resolved = resolver(inst=article, args={
|
||||||
|
# TODO: This should be 'order', not 'o'
|
||||||
|
'o': 'headline'
|
||||||
|
}, info=None)
|
||||||
|
assert 'WHERE' not in str(resolved.query)
|
||||||
|
assert 'ORDER BY' in str(resolved.query)
|
||||||
|
assert '"headline" ASC' in str(resolved.query)
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_order_not_available():
|
||||||
|
reporter = Reporter(id=1, first_name='Cookie Monster')
|
||||||
|
resolver = FilterConnectionResolver(ReporterNode,
|
||||||
|
filterset_class=ReporterFilter)
|
||||||
|
resolved = resolver(inst=reporter, args={
|
||||||
|
# TODO: This should be 'order', not 'o'
|
||||||
|
'o': 'last_name'
|
||||||
|
}, info=None)
|
||||||
|
assert 'WHERE' not in str(resolved.query)
|
||||||
|
assert 'ORDER BY' not in str(resolved.query)
|
|
@ -1,11 +1,8 @@
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
from py.test import raises
|
|
||||||
from django.db.models import Manager
|
from django.db.models import Manager
|
||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
|
|
||||||
from graphene.contrib.django import DjangoNode
|
from graphene.contrib.django import DjangoNode
|
||||||
from graphene.contrib.django.resolvers import SimpleQuerySetConnectionResolver, FilterConnectionResolver
|
from graphene.contrib.django.resolvers import SimpleQuerySetConnectionResolver
|
||||||
from graphene.contrib.django.tests.filters import ReporterFilter, ArticleFilter
|
|
||||||
from graphene.contrib.django.tests.models import Reporter, Article
|
from graphene.contrib.django.tests.models import Reporter, Article
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,74 +56,3 @@ def test_simple_order():
|
||||||
assert 'WHERE' not in str(resolved.query)
|
assert 'WHERE' not in str(resolved.query)
|
||||||
assert 'ORDER BY' in str(resolved.query)
|
assert 'ORDER BY' in str(resolved.query)
|
||||||
assert '"last_name" ASC' in str(resolved.query)
|
assert '"last_name" ASC' in str(resolved.query)
|
||||||
|
|
||||||
|
|
||||||
def test_filter_get_filterset_class_explicit():
|
|
||||||
reporter = Reporter(id=1, first_name='Cookie Monster')
|
|
||||||
resolver = FilterConnectionResolver(ReporterNode,
|
|
||||||
filterset_class=ReporterFilter)
|
|
||||||
resolver(inst=reporter, args={}, info=None)
|
|
||||||
assert issubclass(resolver.get_filterset_class(), ReporterFilter), \
|
|
||||||
'ReporterFilter not returned'
|
|
||||||
|
|
||||||
|
|
||||||
def test_filter_get_filterset_class_implicit():
|
|
||||||
reporter = Reporter(id=1, first_name='Cookie Monster')
|
|
||||||
resolver = FilterConnectionResolver(ReporterNode)
|
|
||||||
resolver(inst=reporter, args={}, info=None)
|
|
||||||
assert resolver.get_filterset_class().__name__ == 'ReporterFilterSet'
|
|
||||||
|
|
||||||
|
|
||||||
def test_filter_get_filterset_class_error():
|
|
||||||
reporter = Reporter(id=1, first_name='Cookie Monster')
|
|
||||||
resolver = FilterConnectionResolver(ReporterNode)
|
|
||||||
resolver.model = None
|
|
||||||
with raises(ImproperlyConfigured) as excinfo:
|
|
||||||
resolver(inst=reporter, args={}, info=None)
|
|
||||||
assert "Neither 'filterset_class' or 'model' available" in str(excinfo.value)
|
|
||||||
|
|
||||||
|
|
||||||
def test_filter_filter():
|
|
||||||
reporter = Reporter(id=1, first_name='Cookie Monster')
|
|
||||||
resolver = FilterConnectionResolver(ReporterNode,
|
|
||||||
filterset_class=ReporterFilter)
|
|
||||||
resolved = resolver(inst=reporter, args={
|
|
||||||
'first_name': 'Elmo'
|
|
||||||
}, info=None)
|
|
||||||
assert '"first_name" = Elmo' in str(resolved.query)
|
|
||||||
assert 'ORDER BY' not in str(resolved.query)
|
|
||||||
|
|
||||||
|
|
||||||
def test_filter_filter_contains():
|
|
||||||
article = Article(id=1, headline='Cookie Monster eats fruit')
|
|
||||||
resolver = FilterConnectionResolver(ArticleNode,
|
|
||||||
filterset_class=ArticleFilter)
|
|
||||||
resolved = resolver(inst=article, args={
|
|
||||||
'headline__icontains': 'Elmo'
|
|
||||||
}, info=None)
|
|
||||||
assert '"headline" LIKE %Elmo%' in str(resolved.query)
|
|
||||||
|
|
||||||
|
|
||||||
def test_filter_order():
|
|
||||||
article = Article(id=1, headline='Cookie Monster eats fruit')
|
|
||||||
resolver = FilterConnectionResolver(ArticleNode,
|
|
||||||
filterset_class=ArticleFilter)
|
|
||||||
resolved = resolver(inst=article, args={
|
|
||||||
# TODO: This should be 'order', not 'o'
|
|
||||||
'o': 'headline'
|
|
||||||
}, info=None)
|
|
||||||
assert 'WHERE' not in str(resolved.query)
|
|
||||||
assert 'ORDER BY' in str(resolved.query)
|
|
||||||
assert '"headline" ASC' in str(resolved.query)
|
|
||||||
|
|
||||||
|
|
||||||
def test_filter_order_not_available():
|
|
||||||
reporter = Reporter(id=1, first_name='Cookie Monster')
|
|
||||||
resolver = FilterConnectionResolver(ReporterNode,
|
|
||||||
filterset_class=ReporterFilter)
|
|
||||||
resolved = resolver(inst=reporter, args={
|
|
||||||
# TODO: This should be 'order', not 'o'
|
|
||||||
'o': 'last_name'
|
|
||||||
}, info=None)
|
|
||||||
assert 'WHERE' not in str(resolved.query)
|
|
||||||
assert 'ORDER BY' not in str(resolved.query)
|
|
||||||
|
|
3
setup.py
3
setup.py
|
@ -57,10 +57,9 @@ setup(
|
||||||
'six>=1.10.0',
|
'six>=1.10.0',
|
||||||
'graphql-core==0.4.9',
|
'graphql-core==0.4.9',
|
||||||
'graphql-relay==0.3.3',
|
'graphql-relay==0.3.3',
|
||||||
'django_filter>=0.10.0',
|
|
||||||
],
|
],
|
||||||
tests_require=[
|
tests_require=[
|
||||||
'django-filter>=0.11.0',
|
'django-filter>=0.10.0',
|
||||||
'pytest>=2.7.2',
|
'pytest>=2.7.2',
|
||||||
'pytest-django',
|
'pytest-django',
|
||||||
'mock',
|
'mock',
|
||||||
|
|
Loading…
Reference in New Issue
Block a user