mirror of
https://github.com/graphql-python/graphene.git
synced 2025-02-02 12:44:15 +03:00
Merge pull request #2 from syrusakbary/feature/django
Improved integration with Django
This commit is contained in:
commit
2264c3dce2
|
@ -80,13 +80,13 @@ matrix:
|
|||
fast_finish: true
|
||||
include:
|
||||
- python: '2.7'
|
||||
env: DJANGO_VERSION=1.6
|
||||
env: TEST_TYPE=build DJANGO_VERSION=1.6
|
||||
- python: '2.7'
|
||||
env: DJANGO_VERSION=1.7
|
||||
env: TEST_TYPE=build DJANGO_VERSION=1.7
|
||||
- python: '2.7'
|
||||
env: DJANGO_VERSION=1.8
|
||||
env: TEST_TYPE=build DJANGO_VERSION=1.8
|
||||
- python: '2.7'
|
||||
env: DJANGO_VERSION=1.9
|
||||
env: TEST_TYPE=build DJANGO_VERSION=1.9
|
||||
- python: '2.7'
|
||||
env: TEST_TYPE=build_website
|
||||
- python: '2.7'
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#!/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
|
||||
autopep8 ./examples/ ./graphene/ -r --in-place --experimental --aggressive --max-line-length 120
|
||||
isort -rc ./examples/ ./graphene/
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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(Category)
|
|
@ -2,8 +2,8 @@
|
|||
# Generated by Django 1.9 on 2015-12-04 18:15
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
|
@ -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.types import DjangoNode
|
||||
|
||||
from cookbook.ingredients.models import Category, Ingredient
|
||||
|
||||
|
||||
# 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)
|
||||
class CategoryNode(DjangoNode):
|
||||
|
||||
class Meta:
|
||||
model = Category
|
||||
filter_fields = ['name', 'ingredients']
|
||||
|
@ -15,6 +15,7 @@ class CategoryNode(DjangoNode):
|
|||
|
||||
|
||||
class IngredientNode(DjangoNode):
|
||||
|
||||
class Meta:
|
||||
model = Ingredient
|
||||
# Allow for some more advanced filtering here
|
|
@ -1,6 +1,5 @@
|
|||
import graphene
|
||||
|
||||
import cookbook.ingredients.schema
|
||||
import graphene
|
||||
|
||||
|
||||
class Query(cookbook.ingredients.schema.Query):
|
|
@ -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.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from graphene.contrib.django.views import GraphQLView
|
||||
|
||||
from cookbook.schema import schema
|
||||
from graphene.contrib.django.views import GraphQLView
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^admin/', admin.site.urls),
|
|
@ -1,28 +1,44 @@
|
|||
import warnings
|
||||
|
||||
from ...core.exceptions import SkipField
|
||||
from ...core.fields import Field
|
||||
from ...core.types.base import FieldType
|
||||
from ...core.types.definitions import List
|
||||
from ...relay import ConnectionField
|
||||
from ...relay.utils import is_node
|
||||
from .filter.fields import DjangoFilterConnectionField
|
||||
from .utils import get_type_for_model
|
||||
from .utils import get_type_for_model, maybe_queryset
|
||||
|
||||
|
||||
class DjangoConnectionField(ConnectionField):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
cls = self.__class__
|
||||
warnings.warn("Using {} will be not longer supported."
|
||||
" Use relay.ConnectionField instead".format(cls.__name__),
|
||||
FutureWarning)
|
||||
self.on = kwargs.pop('on', False)
|
||||
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):
|
||||
|
||||
def internal_type(self, schema):
|
||||
from .filter.fields import DjangoFilterConnectionField
|
||||
|
||||
model_field = self.type
|
||||
field_object_type = model_field.get_object_type(schema)
|
||||
if not field_object_type:
|
||||
|
@ -31,7 +47,7 @@ class ConnectionOrListField(Field):
|
|||
if field_object_type._meta.filter_fields:
|
||||
field = DjangoFilterConnectionField(field_object_type)
|
||||
else:
|
||||
field = ConnectionField(field_object_type)
|
||||
field = DjangoConnectionField(field_object_type)
|
||||
else:
|
||||
field = Field(List(field_object_type))
|
||||
field.contribute_to_class(self.object_type, self.attname)
|
||||
|
|
|
@ -8,8 +8,6 @@ if not DJANGO_FILTER_INSTALLED:
|
|||
|
||||
from .fields import DjangoFilterConnectionField
|
||||
from .filterset import GrapheneFilterSet, GlobalIDFilter, GlobalIDMultipleChoiceFilter
|
||||
from .resolvers import FilterConnectionResolver
|
||||
|
||||
__all__ = ['DjangoFilterConnectionField', 'GrapheneFilterSet',
|
||||
'GlobalIDFilter', 'GlobalIDMultipleChoiceFilter',
|
||||
'FilterConnectionResolver']
|
||||
'GlobalIDFilter', 'GlobalIDMultipleChoiceFilter']
|
||||
|
|
|
@ -1,25 +1,36 @@
|
|||
from graphene.contrib.django.filter.resolvers import FilterConnectionResolver
|
||||
from graphene.contrib.django.utils import get_filtering_args_from_filterset
|
||||
from graphene.relay import ConnectionField
|
||||
from ..fields import DjangoConnectionField
|
||||
from .utils import get_filtering_args_from_filterset, get_filterset_class
|
||||
|
||||
|
||||
class DjangoFilterConnectionField(ConnectionField):
|
||||
class DjangoFilterConnectionField(DjangoConnectionField):
|
||||
|
||||
def __init__(self, type, on=None, fields=None, order_by=None,
|
||||
extra_filter_meta=None, filterset_class=None, resolver=None,
|
||||
def __init__(self, type, fields=None, order_by=None,
|
||||
extra_filter_meta=None, filterset_class=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)
|
||||
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)
|
||||
if extra_filter_meta:
|
||||
meta.update(extra_filter_meta)
|
||||
self.filterset_class = get_filterset_class(filterset_class, **meta)
|
||||
self.filtering_args = get_filtering_args_from_filterset(self.filterset_class, type)
|
||||
kwargs.setdefault('args', {})
|
||||
kwargs['args'].update(**filtering_args)
|
||||
super(DjangoFilterConnectionField, self).__init__(type, resolver, *args, **kwargs)
|
||||
kwargs['args'].update(**self.filtering_args)
|
||||
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)
|
||||
|
|
|
@ -2,12 +2,12 @@ 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 graphql_relay.node.node import from_global_id
|
||||
|
||||
from graphene.contrib.django.forms import (GlobalIDFormField,
|
||||
GlobalIDMultipleChoiceField)
|
||||
from graphql_relay.node.node import from_global_id
|
||||
|
||||
|
||||
class GlobalIDFilter(Filter):
|
||||
|
|
|
@ -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
|
|
@ -1,4 +1,5 @@
|
|||
import django_filters
|
||||
|
||||
from graphene.contrib.django.tests.models import Article, Pet, Reporter
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
from graphql.core.execution.base import ResolveInfo, ExecutionContext
|
||||
|
||||
from graphene import ObjectType, Schema
|
||||
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.utils import DJANGO_FILTER_INSTALLED
|
||||
from graphene.relay import NodeField
|
||||
from graphene.utils import ProxySnakeDict
|
||||
|
||||
pytestmark = []
|
||||
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')
|
||||
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)
|
||||
a2 = Article.objects.create(headline='a2', pub_date=datetime.now(), reporter=r2)
|
||||
Article.objects.create(headline='a1', pub_date=datetime.now(), reporter=r1)
|
||||
Article.objects.create(headline='a2', pub_date=datetime.now(), reporter=r2)
|
||||
|
||||
query = '''
|
||||
query {
|
||||
|
@ -217,7 +215,7 @@ def test_filter_filterset_related_results():
|
|||
|
||||
def test_global_id_field_implicit():
|
||||
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']
|
||||
assert isinstance(id_filter, GlobalIDFilter)
|
||||
assert id_filter.field_class == GlobalIDFormField
|
||||
|
@ -231,7 +229,7 @@ def test_global_id_field_explicit():
|
|||
fields = ['id']
|
||||
|
||||
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']
|
||||
assert isinstance(id_filter, GlobalIDFilter)
|
||||
assert id_filter.field_class == GlobalIDFormField
|
||||
|
@ -239,7 +237,7 @@ def test_global_id_field_explicit():
|
|||
|
||||
def test_global_id_field_relation():
|
||||
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']
|
||||
assert isinstance(id_filter, GlobalIDFilter)
|
||||
assert id_filter.field_class == GlobalIDFormField
|
||||
|
@ -247,7 +245,7 @@ def test_global_id_field_relation():
|
|||
|
||||
def test_global_id_multiple_field_implicit():
|
||||
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']
|
||||
assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter)
|
||||
assert multiple_filter.field_class == GlobalIDMultipleChoiceField
|
||||
|
@ -261,7 +259,7 @@ def test_global_id_multiple_field_explicit():
|
|||
fields = ['pets']
|
||||
|
||||
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']
|
||||
assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter)
|
||||
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():
|
||||
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']
|
||||
assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter)
|
||||
assert multiple_filter.field_class == GlobalIDMultipleChoiceField
|
||||
|
@ -283,7 +281,7 @@ def test_global_id_multiple_field_explicit_reverse():
|
|||
fields = ['articles']
|
||||
|
||||
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']
|
||||
assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter)
|
||||
assert multiple_filter.field_class == GlobalIDMultipleChoiceField
|
||||
|
|
|
@ -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)
|
31
graphene/contrib/django/filter/utils.py
Normal file
31
graphene/contrib/django/filter/utils.py
Normal 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)
|
|
@ -3,7 +3,6 @@ import binascii
|
|||
from django.core.exceptions import ValidationError
|
||||
from django.forms import CharField, Field, IntegerField, MultipleChoiceField
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from graphql_relay import from_global_id
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
import importlib
|
||||
import json
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Dump Graphene schema JSON to 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)
|
|
@ -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)
|
|
@ -1,7 +1,7 @@
|
|||
from py.test import raises
|
||||
from tests.utils import assert_equal_lists
|
||||
|
||||
from graphene.contrib.django import DjangoObjectType
|
||||
from tests.utils import assert_equal_lists
|
||||
|
||||
from .models import Reporter
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
from graphql.core.type import GraphQLObjectType
|
||||
from mock import patch
|
||||
from tests.utils import assert_equal_lists
|
||||
|
||||
from graphene import Schema
|
||||
from graphene.contrib.django.types import DjangoNode, DjangoObjectType
|
||||
from graphene.core.fields import Field
|
||||
from graphene.core.types.scalars import Int
|
||||
from graphene.relay.fields import GlobalIDField
|
||||
from tests.utils import assert_equal_lists
|
||||
|
||||
from .models import Article, Reporter
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ from ...core.classtypes.objecttype import ObjectType, ObjectTypeMeta
|
|||
from ...relay.types import Connection, Node, NodeMeta
|
||||
from .converter import convert_django_field
|
||||
from .options import DjangoOptions
|
||||
from .utils import get_reverse_fields, maybe_queryset
|
||||
from .utils import get_reverse_fields
|
||||
|
||||
|
||||
class DjangoObjectTypeMeta(ObjectTypeMeta):
|
||||
|
@ -82,11 +82,7 @@ class DjangoObjectType(six.with_metaclass(
|
|||
|
||||
|
||||
class DjangoConnection(Connection):
|
||||
|
||||
@classmethod
|
||||
def from_list(cls, iterable, *args, **kwargs):
|
||||
iterable = maybe_queryset(iterable)
|
||||
return super(DjangoConnection, cls).from_list(iterable, *args, **kwargs)
|
||||
pass
|
||||
|
||||
|
||||
class DjangoNodeMeta(DjangoObjectTypeMeta, NodeMeta):
|
||||
|
@ -112,5 +108,3 @@ class DjangoNode(six.with_metaclass(
|
|||
return cls(instance)
|
||||
except cls._meta.model.DoesNotExist:
|
||||
return None
|
||||
|
||||
connection_type = DjangoConnection
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import six
|
||||
from django.db import models
|
||||
from django.db.models.manager import Manager
|
||||
from django.db.models.query import QuerySet
|
||||
|
||||
from graphene import Argument, String
|
||||
from graphene.utils import LazyList
|
||||
|
||||
from .compat import RelatedObject
|
||||
|
@ -56,26 +54,6 @@ def maybe_queryset(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):
|
||||
if hasattr(field, 'rel'):
|
||||
# Django 1.6, 1.7
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
from graphql.core import graphql
|
||||
from py.test import raises
|
||||
from tests.utils import assert_equal_lists
|
||||
|
||||
from graphene import Interface, List, ObjectType, Schema, String
|
||||
from graphene.core.fields import Field
|
||||
from graphene.core.types.base import LazyType
|
||||
from tests.utils import assert_equal_lists
|
||||
|
||||
schema = Schema(name='My own schema')
|
||||
|
||||
|
|
|
@ -51,6 +51,16 @@ class Field(NamedType, OrderedType):
|
|||
def resolver(self):
|
||||
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):
|
||||
resolve_fn_name = 'resolve_%s' % self.attname
|
||||
if hasattr(self.object_type, resolve_fn_name):
|
||||
|
|
|
@ -23,15 +23,15 @@ class ConnectionField(Field):
|
|||
self.connection_type = connection_type
|
||||
self.edge_type = edge_type
|
||||
|
||||
def wrap_resolved(self, value, instance, args, info):
|
||||
return value
|
||||
|
||||
def resolver(self, instance, args, info):
|
||||
schema = info.schema.graphene_schema
|
||||
connection_type = self.get_type(schema)
|
||||
resolved = super(ConnectionField, self).resolver(instance, args, info)
|
||||
if isinstance(resolved, connection_type):
|
||||
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)
|
||||
|
||||
def get_connection_type(self, node):
|
||||
|
|
|
@ -4,7 +4,6 @@ from collections import Iterable
|
|||
from functools import wraps
|
||||
|
||||
import six
|
||||
|
||||
from graphql_relay.connection.arrayconnection import connection_from_list
|
||||
from graphql_relay.node.node import to_global_id
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[flake8]
|
||||
exclude = setup.py,docs/*
|
||||
exclude = setup.py,docs/*,examples/cookbook_django/*
|
||||
max-line-length = 120
|
||||
|
||||
[coverage:run]
|
||||
|
|
Loading…
Reference in New Issue
Block a user