Merge pull request #2 from syrusakbary/feature/django

Improved integration with Django
This commit is contained in:
Adam Charnock 2016-01-02 20:36:06 +00:00
commit 2264c3dce2
51 changed files with 136 additions and 349 deletions

View File

@ -80,13 +80,13 @@ matrix:
fast_finish: true fast_finish: true
include: include:
- python: '2.7' - python: '2.7'
env: DJANGO_VERSION=1.6 env: TEST_TYPE=build DJANGO_VERSION=1.6
- python: '2.7' - python: '2.7'
env: DJANGO_VERSION=1.7 env: TEST_TYPE=build DJANGO_VERSION=1.7
- python: '2.7' - python: '2.7'
env: DJANGO_VERSION=1.8 env: TEST_TYPE=build DJANGO_VERSION=1.8
- python: '2.7' - python: '2.7'
env: DJANGO_VERSION=1.9 env: TEST_TYPE=build DJANGO_VERSION=1.9
- python: '2.7' - python: '2.7'
env: TEST_TYPE=build_website env: TEST_TYPE=build_website
- python: '2.7' - python: '2.7'

View File

@ -1,5 +1,7 @@
#!/bin/bash #!/bin/bash
# Install the required scripts with
# pip install autoflake autopep8 isort
autoflake ./examples/ ./graphene/ -r --remove-unused-variables --remove-all-unused-imports --in-place autoflake ./examples/ ./graphene/ -r --remove-unused-variables --remove-all-unused-imports --in-place
autopep8 ./examples/ ./graphene/ -r --in-place --experimental --aggressive --max-line-length 120 autopep8 ./examples/ ./graphene/ -r --in-place --experimental --aggressive --max-line-length 120
isort -rc ./examples/ ./graphene/ isort -rc ./examples/ ./graphene/

View File

@ -1,6 +1,6 @@
from django.contrib import admin from django.contrib import admin
from cookbook.ingredients.models import Ingredient, Category from cookbook.ingredients.models import Category, Ingredient
admin.site.register(Ingredient) admin.site.register(Ingredient)
admin.site.register(Category) admin.site.register(Category)

View File

@ -2,8 +2,8 @@
# Generated by Django 1.9 on 2015-12-04 18:15 # Generated by Django 1.9 on 2015-12-04 18:15
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,13 +1,13 @@
from graphene import relay, ObjectType from cookbook.ingredients.models import Category, Ingredient
from graphene import ObjectType, relay
from graphene.contrib.django.filter import DjangoFilterConnectionField from graphene.contrib.django.filter import DjangoFilterConnectionField
from graphene.contrib.django.types import DjangoNode from graphene.contrib.django.types import DjangoNode
from cookbook.ingredients.models import Category, Ingredient
# Graphene will automatically map the User model's fields onto the UserType. # Graphene will automatically map the User model's fields onto the UserType.
# This is configured in the UserType's Meta class (as you can see below) # This is configured in the UserType's Meta class (as you can see below)
class CategoryNode(DjangoNode): class CategoryNode(DjangoNode):
class Meta: class Meta:
model = Category model = Category
filter_fields = ['name', 'ingredients'] filter_fields = ['name', 'ingredients']
@ -15,6 +15,7 @@ class CategoryNode(DjangoNode):
class IngredientNode(DjangoNode): class IngredientNode(DjangoNode):
class Meta: class Meta:
model = Ingredient model = Ingredient
# Allow for some more advanced filtering here # Allow for some more advanced filtering here

View File

@ -1,6 +1,5 @@
import graphene
import cookbook.ingredients.schema import cookbook.ingredients.schema
import graphene
class Query(cookbook.ingredients.schema.Query): class Query(cookbook.ingredients.schema.Query):

View File

@ -1,10 +1,9 @@
from django.conf.urls import url, include from django.conf.urls import include, url
from django.contrib import admin from django.contrib import admin
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from graphene.contrib.django.views import GraphQLView
from cookbook.schema import schema from cookbook.schema import schema
from graphene.contrib.django.views import GraphQLView
urlpatterns = [ urlpatterns = [
url(r'^admin/', admin.site.urls), url(r'^admin/', admin.site.urls),

View File

@ -1,28 +1,44 @@
import warnings
from ...core.exceptions import SkipField from ...core.exceptions import SkipField
from ...core.fields import Field from ...core.fields import Field
from ...core.types.base import FieldType from ...core.types.base import FieldType
from ...core.types.definitions import List from ...core.types.definitions import List
from ...relay import ConnectionField from ...relay import ConnectionField
from ...relay.utils import is_node from ...relay.utils import is_node
from .filter.fields import DjangoFilterConnectionField from .utils import get_type_for_model, maybe_queryset
from .utils import get_type_for_model
class DjangoConnectionField(ConnectionField): class DjangoConnectionField(ConnectionField):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
cls = self.__class__ self.on = kwargs.pop('on', False)
warnings.warn("Using {} will be not longer supported."
" Use relay.ConnectionField instead".format(cls.__name__),
FutureWarning)
return super(DjangoConnectionField, self).__init__(*args, **kwargs) return super(DjangoConnectionField, self).__init__(*args, **kwargs)
@property
def model(self):
return self.type._meta.model
def get_manager(self):
if self.on:
return getattr(self.model, self.on)
else:
return self.model._default_manager
def get_queryset(self, resolved_qs, args, info):
return resolved_qs
def from_list(self, connection_type, resolved, args, info):
if not resolved:
resolved = self.get_manager()
resolved_qs = maybe_queryset(resolved)
qs = self.get_queryset(resolved_qs, args, info)
return super(DjangoConnectionField, self).from_list(connection_type, qs, args, info)
class ConnectionOrListField(Field): class ConnectionOrListField(Field):
def internal_type(self, schema): def internal_type(self, schema):
from .filter.fields import DjangoFilterConnectionField
model_field = self.type model_field = self.type
field_object_type = model_field.get_object_type(schema) field_object_type = model_field.get_object_type(schema)
if not field_object_type: if not field_object_type:
@ -31,7 +47,7 @@ class ConnectionOrListField(Field):
if field_object_type._meta.filter_fields: if field_object_type._meta.filter_fields:
field = DjangoFilterConnectionField(field_object_type) field = DjangoFilterConnectionField(field_object_type)
else: else:
field = ConnectionField(field_object_type) field = DjangoConnectionField(field_object_type)
else: else:
field = Field(List(field_object_type)) field = Field(List(field_object_type))
field.contribute_to_class(self.object_type, self.attname) field.contribute_to_class(self.object_type, self.attname)

View File

@ -8,8 +8,6 @@ if not DJANGO_FILTER_INSTALLED:
from .fields import DjangoFilterConnectionField from .fields import DjangoFilterConnectionField
from .filterset import GrapheneFilterSet, GlobalIDFilter, GlobalIDMultipleChoiceFilter from .filterset import GrapheneFilterSet, GlobalIDFilter, GlobalIDMultipleChoiceFilter
from .resolvers import FilterConnectionResolver
__all__ = ['DjangoFilterConnectionField', 'GrapheneFilterSet', __all__ = ['DjangoFilterConnectionField', 'GrapheneFilterSet',
'GlobalIDFilter', 'GlobalIDMultipleChoiceFilter', 'GlobalIDFilter', 'GlobalIDMultipleChoiceFilter']
'FilterConnectionResolver']

View File

@ -1,25 +1,36 @@
from graphene.contrib.django.filter.resolvers import FilterConnectionResolver from ..fields import DjangoConnectionField
from graphene.contrib.django.utils import get_filtering_args_from_filterset from .utils import get_filtering_args_from_filterset, get_filterset_class
from graphene.relay import ConnectionField
class DjangoFilterConnectionField(ConnectionField): class DjangoFilterConnectionField(DjangoConnectionField):
def __init__(self, type, on=None, fields=None, order_by=None, def __init__(self, type, fields=None, order_by=None,
extra_filter_meta=None, filterset_class=None, resolver=None, extra_filter_meta=None, filterset_class=None,
*args, **kwargs): *args, **kwargs):
if not resolver: self.order_by = order_by or type._meta.filter_order_by
resolver = FilterConnectionResolver( self.fields = fields or type._meta.filter_fields
node=type, meta = dict(model=type._meta.model,
on=on, fields=self.fields,
filterset_class=filterset_class, order_by=self.order_by)
fields=fields, if extra_filter_meta:
order_by=order_by, meta.update(extra_filter_meta)
extra_filter_meta=extra_filter_meta, self.filterset_class = get_filterset_class(filterset_class, **meta)
) self.filtering_args = get_filtering_args_from_filterset(self.filterset_class, type)
filtering_args = get_filtering_args_from_filterset(resolver.get_filterset_class(), type)
kwargs.setdefault('args', {}) kwargs.setdefault('args', {})
kwargs['args'].update(**filtering_args) kwargs['args'].update(**self.filtering_args)
super(DjangoFilterConnectionField, self).__init__(type, resolver, *args, **kwargs) super(DjangoFilterConnectionField, self).__init__(type, *args, **kwargs)
def get_queryset(self, qs, args, info):
filterset_class = self.filterset_class
filter_kwargs = self.get_filter_kwargs(args)
order = self.get_order(args)
if order:
qs = qs.order_by(order)
return filterset_class(data=filter_kwargs, queryset=qs)
def get_filter_kwargs(self, args):
return {k: v for k, v in args.items() if k in self.filtering_args}
def get_order(self, args):
return args.get('order_by', None)

View File

@ -2,12 +2,12 @@ import six
from django.conf import settings 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 FilterSet, FilterSetMetaclass
from graphql_relay.node.node import from_global_id
from graphene.contrib.django.forms import (GlobalIDFormField, from graphene.contrib.django.forms import (GlobalIDFormField,
GlobalIDMultipleChoiceField) GlobalIDMultipleChoiceField)
from graphql_relay.node.node import from_global_id
class GlobalIDFilter(Filter): class GlobalIDFilter(Filter):

View File

@ -1,64 +0,0 @@
from django.core.exceptions import ImproperlyConfigured
from graphene.contrib.django.filter.filterset import (custom_filterset_factory,
setup_filterset)
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 or node._meta.filter_fields
self.order_by = order_by or node._meta.filter_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

View File

@ -1,4 +1,5 @@
import django_filters import django_filters
from graphene.contrib.django.tests.models import Article, Pet, Reporter from graphene.contrib.django.tests.models import Article, Pet, Reporter

View File

@ -1,7 +1,6 @@
from datetime import datetime from datetime import datetime
import pytest import pytest
from graphql.core.execution.base import ResolveInfo, ExecutionContext
from graphene import ObjectType, Schema from graphene import ObjectType, Schema
from graphene.contrib.django import DjangoNode from graphene.contrib.django import DjangoNode
@ -10,7 +9,6 @@ from graphene.contrib.django.forms import (GlobalIDFormField,
from graphene.contrib.django.tests.models import Article, Pet, Reporter from graphene.contrib.django.tests.models import Article, Pet, Reporter
from graphene.contrib.django.utils import DJANGO_FILTER_INSTALLED from graphene.contrib.django.utils import DJANGO_FILTER_INSTALLED
from graphene.relay import NodeField from graphene.relay import NodeField
from graphene.utils import ProxySnakeDict
pytestmark = [] pytestmark = []
if DJANGO_FILTER_INSTALLED: if DJANGO_FILTER_INSTALLED:
@ -187,8 +185,8 @@ def test_filter_filterset_related_results():
r1 = Reporter.objects.create(first_name='r1', last_name='r1', email='r1@test.com') r1 = Reporter.objects.create(first_name='r1', last_name='r1', email='r1@test.com')
r2 = Reporter.objects.create(first_name='r2', last_name='r2', email='r2@test.com') r2 = Reporter.objects.create(first_name='r2', last_name='r2', email='r2@test.com')
a1 = Article.objects.create(headline='a1', pub_date=datetime.now(), reporter=r1) Article.objects.create(headline='a1', pub_date=datetime.now(), reporter=r1)
a2 = Article.objects.create(headline='a2', pub_date=datetime.now(), reporter=r2) Article.objects.create(headline='a2', pub_date=datetime.now(), reporter=r2)
query = ''' query = '''
query { query {
@ -217,7 +215,7 @@ def test_filter_filterset_related_results():
def test_global_id_field_implicit(): def test_global_id_field_implicit():
field = DjangoFilterConnectionField(ArticleNode, fields=['id']) field = DjangoFilterConnectionField(ArticleNode, fields=['id'])
filterset_class = field.resolver_fn.get_filterset_class() filterset_class = field.filterset_class
id_filter = filterset_class.base_filters['id'] id_filter = filterset_class.base_filters['id']
assert isinstance(id_filter, GlobalIDFilter) assert isinstance(id_filter, GlobalIDFilter)
assert id_filter.field_class == GlobalIDFormField assert id_filter.field_class == GlobalIDFormField
@ -231,7 +229,7 @@ def test_global_id_field_explicit():
fields = ['id'] fields = ['id']
field = DjangoFilterConnectionField(ArticleNode, filterset_class=ArticleIdFilter) field = DjangoFilterConnectionField(ArticleNode, filterset_class=ArticleIdFilter)
filterset_class = field.resolver_fn.get_filterset_class() filterset_class = field.filterset_class
id_filter = filterset_class.base_filters['id'] id_filter = filterset_class.base_filters['id']
assert isinstance(id_filter, GlobalIDFilter) assert isinstance(id_filter, GlobalIDFilter)
assert id_filter.field_class == GlobalIDFormField assert id_filter.field_class == GlobalIDFormField
@ -239,7 +237,7 @@ def test_global_id_field_explicit():
def test_global_id_field_relation(): def test_global_id_field_relation():
field = DjangoFilterConnectionField(ArticleNode, fields=['reporter']) field = DjangoFilterConnectionField(ArticleNode, fields=['reporter'])
filterset_class = field.resolver_fn.get_filterset_class() filterset_class = field.filterset_class
id_filter = filterset_class.base_filters['reporter'] id_filter = filterset_class.base_filters['reporter']
assert isinstance(id_filter, GlobalIDFilter) assert isinstance(id_filter, GlobalIDFilter)
assert id_filter.field_class == GlobalIDFormField assert id_filter.field_class == GlobalIDFormField
@ -247,7 +245,7 @@ def test_global_id_field_relation():
def test_global_id_multiple_field_implicit(): def test_global_id_multiple_field_implicit():
field = DjangoFilterConnectionField(ReporterNode, fields=['pets']) field = DjangoFilterConnectionField(ReporterNode, fields=['pets'])
filterset_class = field.resolver_fn.get_filterset_class() filterset_class = field.filterset_class
multiple_filter = filterset_class.base_filters['pets'] multiple_filter = filterset_class.base_filters['pets']
assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter) assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter)
assert multiple_filter.field_class == GlobalIDMultipleChoiceField assert multiple_filter.field_class == GlobalIDMultipleChoiceField
@ -261,7 +259,7 @@ def test_global_id_multiple_field_explicit():
fields = ['pets'] fields = ['pets']
field = DjangoFilterConnectionField(ReporterNode, filterset_class=ReporterPetsFilter) field = DjangoFilterConnectionField(ReporterNode, filterset_class=ReporterPetsFilter)
filterset_class = field.resolver_fn.get_filterset_class() filterset_class = field.filterset_class
multiple_filter = filterset_class.base_filters['pets'] multiple_filter = filterset_class.base_filters['pets']
assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter) assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter)
assert multiple_filter.field_class == GlobalIDMultipleChoiceField assert multiple_filter.field_class == GlobalIDMultipleChoiceField
@ -269,7 +267,7 @@ def test_global_id_multiple_field_explicit():
def test_global_id_multiple_field_implicit_reverse(): def test_global_id_multiple_field_implicit_reverse():
field = DjangoFilterConnectionField(ReporterNode, fields=['articles']) field = DjangoFilterConnectionField(ReporterNode, fields=['articles'])
filterset_class = field.resolver_fn.get_filterset_class() filterset_class = field.filterset_class
multiple_filter = filterset_class.base_filters['articles'] multiple_filter = filterset_class.base_filters['articles']
assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter) assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter)
assert multiple_filter.field_class == GlobalIDMultipleChoiceField assert multiple_filter.field_class == GlobalIDMultipleChoiceField
@ -283,7 +281,7 @@ def test_global_id_multiple_field_explicit_reverse():
fields = ['articles'] fields = ['articles']
field = DjangoFilterConnectionField(ReporterNode, filterset_class=ReporterPetsFilter) field = DjangoFilterConnectionField(ReporterNode, filterset_class=ReporterPetsFilter)
filterset_class = field.resolver_fn.get_filterset_class() filterset_class = field.filterset_class
multiple_filter = filterset_class.base_filters['articles'] multiple_filter = filterset_class.base_filters['articles']
assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter) assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter)
assert multiple_filter.field_class == GlobalIDMultipleChoiceField assert multiple_filter.field_class == GlobalIDMultipleChoiceField

View File

@ -1,82 +0,0 @@
import pytest
from django.core.exceptions import ImproperlyConfigured
from graphene.contrib.django.tests.models import Article, Reporter
from graphene.contrib.django.tests.test_resolvers import (ArticleNode,
ReporterNode)
from graphene.contrib.django.utils import DJANGO_FILTER_INSTALLED
if DJANGO_FILTER_INSTALLED:
from graphene.contrib.django.filter.resolvers import FilterConnectionResolver
from graphene.contrib.django.filter.tests.filters import ArticleFilter, ReporterFilter
else:
pytestmark = pytest.mark.skipif(True, reason='django_filters not installed')
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={
'order_by': '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={
'order_by': 'last_name'
}, info=None)
assert 'WHERE' not in str(resolved.query)
assert 'ORDER BY' not in str(resolved.query)

View File

@ -0,0 +1,31 @@
import six
from ....core.types import Argument, String
from .filterset import custom_filterset_factory, setup_filterset
def get_filtering_args_from_filterset(filterset_class, type):
""" Inspect a FilterSet and produce the arguments to pass to
a Graphene Field. These arguments will be available to
filter against in the GraphQL
"""
from graphene.contrib.django.form_converter import convert_form_field
args = {}
for name, filter_field in six.iteritems(filterset_class.base_filters):
field_type = Argument(convert_form_field(filter_field.field))
args[name] = field_type
# Also add the 'order_by' field
if filterset_class._meta.order_by:
args[filterset_class.order_by_field] = Argument(String())
return args
def get_filterset_class(filterset_class, **meta):
"""Get the class to be used as the FilterSet"""
if filterset_class:
# If were given a FilterSet class, then set it up and
# return it
return setup_filterset(filterset_class)
return custom_filterset_factory(**meta)

View File

@ -3,7 +3,6 @@ import binascii
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.forms import CharField, Field, IntegerField, MultipleChoiceField from django.forms import CharField, Field, IntegerField, MultipleChoiceField
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from graphql_relay import from_global_id from graphql_relay import from_global_id

View File

@ -1,8 +1,8 @@
from django.core.management.base import BaseCommand, CommandError
import importlib import importlib
import json import json
from django.core.management.base import BaseCommand, CommandError
class Command(BaseCommand): class Command(BaseCommand):
help = 'Dump Graphene schema JSON to file' help = 'Dump Graphene schema JSON to file'

View File

@ -1,43 +0,0 @@
class BaseQuerySetConnectionResolver(object):
def __init__(self, node, on=None):
self.node = node
self.model = node._meta.model
# The name of the field on the model which contains the
# manager upon which to perform the query. Optional.
# If omitted the model's default manager will be used.
self.on = on
def __call__(self, inst, args, info):
self.inst = inst
self.args = args
self.info = info
return self.make_query()
def get_manager(self):
if self.on:
return getattr(self.inst, self.on)
else:
return self.model._default_manager
def make_query(self):
raise NotImplemented()
class SimpleQuerySetConnectionResolver(BaseQuerySetConnectionResolver):
# Simple querying without using django-filter (ported from previous gist)
def make_query(self):
filter_kwargs = self.get_filter_kwargs()
query = self.get_manager().filter(**filter_kwargs)
order = self.get_order()
if order:
query = query.order_by(order)
return query
def get_filter_kwargs(self):
ignore = ['first', 'last', 'before', 'after', 'order_by']
return {k: v for k, v in self.args.items() if k not in ignore}
def get_order(self):
return self.args.get('order_by', None)

View File

@ -1,60 +0,0 @@
from django.db.models import Manager
from django.db.models.query import QuerySet
from graphene.contrib.django import DjangoNode
from graphene.contrib.django.resolvers import SimpleQuerySetConnectionResolver
from graphene.contrib.django.tests.models import Article, Reporter
class ReporterNode(DjangoNode):
class Meta:
model = Reporter
class ArticleNode(DjangoNode):
class Meta:
model = Article
def test_simple_resolve():
reporter = Reporter(id=1, first_name='Cookie Monster')
resolver = SimpleQuerySetConnectionResolver(ReporterNode, on='articles')
resolved = resolver(inst=reporter, args={}, info=None)
assert isinstance(resolved, QuerySet), 'Did not resolve to a queryset'
def test_simple_get_manager_related():
reporter = Reporter(id=1, first_name='Cookie Monster')
resolver = SimpleQuerySetConnectionResolver(ReporterNode, on='articles')
resolver(inst=reporter, args={}, info=None)
assert resolver.get_manager().instance == reporter, 'Resolver did not return a RelatedManager'
def test_simple_get_manager_all():
reporter = Reporter(id=1, first_name='Cookie Monster')
resolver = SimpleQuerySetConnectionResolver(ReporterNode)
resolver(inst=reporter, args={}, info=None)
assert isinstance(resolver.get_manager(), Manager), 'Resolver did not return a Manager'
def test_simple_filter():
reporter = Reporter(id=1, first_name='Cookie Monster')
resolver = SimpleQuerySetConnectionResolver(ReporterNode)
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_simple_order():
reporter = Reporter(id=1, first_name='Cookie Monster')
resolver = SimpleQuerySetConnectionResolver(ReporterNode)
resolved = resolver(inst=reporter, args={
'order_by': 'last_name'
}, info=None)
assert 'WHERE' not in str(resolved.query)
assert 'ORDER BY' in str(resolved.query)
assert '"last_name" ASC' in str(resolved.query)

View File

@ -1,7 +1,7 @@
from py.test import raises from py.test import raises
from tests.utils import assert_equal_lists
from graphene.contrib.django import DjangoObjectType from graphene.contrib.django import DjangoObjectType
from tests.utils import assert_equal_lists
from .models import Reporter from .models import Reporter

View File

@ -1,12 +1,12 @@
from graphql.core.type import GraphQLObjectType from graphql.core.type import GraphQLObjectType
from mock import patch from mock import patch
from tests.utils import assert_equal_lists
from graphene import Schema from graphene import Schema
from graphene.contrib.django.types import DjangoNode, DjangoObjectType from graphene.contrib.django.types import DjangoNode, DjangoObjectType
from graphene.core.fields import Field from graphene.core.fields import Field
from graphene.core.types.scalars import Int from graphene.core.types.scalars import Int
from graphene.relay.fields import GlobalIDField from graphene.relay.fields import GlobalIDField
from tests.utils import assert_equal_lists
from .models import Article, Reporter from .models import Article, Reporter

View File

@ -7,7 +7,7 @@ from ...core.classtypes.objecttype import ObjectType, ObjectTypeMeta
from ...relay.types import Connection, Node, NodeMeta from ...relay.types import Connection, Node, NodeMeta
from .converter import convert_django_field from .converter import convert_django_field
from .options import DjangoOptions from .options import DjangoOptions
from .utils import get_reverse_fields, maybe_queryset from .utils import get_reverse_fields
class DjangoObjectTypeMeta(ObjectTypeMeta): class DjangoObjectTypeMeta(ObjectTypeMeta):
@ -82,11 +82,7 @@ class DjangoObjectType(six.with_metaclass(
class DjangoConnection(Connection): class DjangoConnection(Connection):
pass
@classmethod
def from_list(cls, iterable, *args, **kwargs):
iterable = maybe_queryset(iterable)
return super(DjangoConnection, cls).from_list(iterable, *args, **kwargs)
class DjangoNodeMeta(DjangoObjectTypeMeta, NodeMeta): class DjangoNodeMeta(DjangoObjectTypeMeta, NodeMeta):
@ -112,5 +108,3 @@ class DjangoNode(six.with_metaclass(
return cls(instance) return cls(instance)
except cls._meta.model.DoesNotExist: except cls._meta.model.DoesNotExist:
return None return None
connection_type = DjangoConnection

View File

@ -1,9 +1,7 @@
import six
from django.db import models from django.db import models
from django.db.models.manager import Manager from django.db.models.manager import Manager
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from graphene import Argument, String
from graphene.utils import LazyList from graphene.utils import LazyList
from .compat import RelatedObject from .compat import RelatedObject
@ -56,26 +54,6 @@ def maybe_queryset(value):
return value return value
def get_filtering_args_from_filterset(filterset_class, type):
""" Inspect a FilterSet and produce the arguments to pass to
a Graphene Field. These arguments will be available to
filter against in the GraphQL
"""
from graphene.contrib.django.form_converter import convert_form_field
args = {}
for name, filter_field in six.iteritems(filterset_class.base_filters):
field_type = Argument(convert_form_field(filter_field.field))
# Is this correct? I don't quite grok the 'parent' system yet
field_type.mount(type)
args[name] = field_type
# Also add the 'order_by' field
if filterset_class._meta.order_by:
args[filterset_class.order_by_field] = Argument(String())
return args
def get_related_model(field): def get_related_model(field):
if hasattr(field, 'rel'): if hasattr(field, 'rel'):
# Django 1.6, 1.7 # Django 1.6, 1.7

View File

@ -1,10 +1,10 @@
from graphql.core import graphql from graphql.core import graphql
from py.test import raises from py.test import raises
from tests.utils import assert_equal_lists
from graphene import Interface, List, ObjectType, Schema, String from graphene import Interface, List, ObjectType, Schema, String
from graphene.core.fields import Field from graphene.core.fields import Field
from graphene.core.types.base import LazyType from graphene.core.types.base import LazyType
from tests.utils import assert_equal_lists
schema = Schema(name='My own schema') schema = Schema(name='My own schema')

View File

@ -51,6 +51,16 @@ class Field(NamedType, OrderedType):
def resolver(self): def resolver(self):
return self.resolver_fn or self.get_resolver_fn() return self.resolver_fn or self.get_resolver_fn()
@property
def default(self):
if callable(self._default):
return self._default()
return self._default
@default.setter
def default(self, value):
self._default = value
def get_resolver_fn(self): def get_resolver_fn(self):
resolve_fn_name = 'resolve_%s' % self.attname resolve_fn_name = 'resolve_%s' % self.attname
if hasattr(self.object_type, resolve_fn_name): if hasattr(self.object_type, resolve_fn_name):

View File

@ -23,15 +23,15 @@ class ConnectionField(Field):
self.connection_type = connection_type self.connection_type = connection_type
self.edge_type = edge_type self.edge_type = edge_type
def wrap_resolved(self, value, instance, args, info):
return value
def resolver(self, instance, args, info): def resolver(self, instance, args, info):
schema = info.schema.graphene_schema schema = info.schema.graphene_schema
connection_type = self.get_type(schema) connection_type = self.get_type(schema)
resolved = super(ConnectionField, self).resolver(instance, args, info) resolved = super(ConnectionField, self).resolver(instance, args, info)
if isinstance(resolved, connection_type): if isinstance(resolved, connection_type):
return resolved return resolved
return self.from_list(connection_type, resolved, args, info)
def from_list(self, connection_type, resolved, args, info):
return connection_type.from_list(resolved, args, info) return connection_type.from_list(resolved, args, info)
def get_connection_type(self, node): def get_connection_type(self, node):

View File

@ -4,7 +4,6 @@ from collections import Iterable
from functools import wraps from functools import wraps
import six import six
from graphql_relay.connection.arrayconnection import connection_from_list from graphql_relay.connection.arrayconnection import connection_from_list
from graphql_relay.node.node import to_global_id from graphql_relay.node.node import to_global_id

View File

@ -1,5 +1,5 @@
[flake8] [flake8]
exclude = setup.py,docs/* exclude = setup.py,docs/*,examples/cookbook_django/*
max-line-length = 120 max-line-length = 120
[coverage:run] [coverage:run]

View File

@ -55,7 +55,7 @@ setup(
install_requires=[ install_requires=[
'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',
], ],
tests_require=[ tests_require=[