mirror of
https://github.com/graphql-python/graphene.git
synced 2024-11-23 01:56:54 +03:00
Improved Django integration
This commit is contained in:
parent
37ed617fce
commit
f9303dab72
|
@ -1,5 +1,5 @@
|
|||
from cookbook.ingredients.models import Category, Ingredient
|
||||
from graphene import ObjectType, relay
|
||||
from graphene import ObjectType, Field
|
||||
from graphene_django.filter import DjangoFilterConnectionField
|
||||
from graphene_django.types import DjangoNode, DjangoObjectType
|
||||
|
||||
|
@ -29,8 +29,8 @@ class IngredientNode(DjangoNode, DjangoObjectType):
|
|||
|
||||
|
||||
class Query(ObjectType):
|
||||
category = relay.NodeField(CategoryNode)
|
||||
category = Field(CategoryNode)
|
||||
all_categories = DjangoFilterConnectionField(CategoryNode)
|
||||
|
||||
ingredient = relay.NodeField(IngredientNode)
|
||||
ingredient = Field(IngredientNode)
|
||||
all_ingredients = DjangoFilterConnectionField(IngredientNode)
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import cookbook.ingredients.schema
|
||||
import graphene
|
||||
import cookbook.ingredients.schema
|
||||
|
||||
# print cookbook.ingredients.schema.Query._meta.graphql_type.get_fields()['allIngredients'].args
|
||||
|
||||
class Query(cookbook.ingredients.schema.Query):
|
||||
pass
|
||||
|
||||
schema = graphene.Schema(name='Cookbook Schema', query=Query)
|
||||
schema = graphene.Schema(query=Query)
|
||||
|
|
|
@ -3,7 +3,7 @@ from django.contrib import admin
|
|||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from cookbook.schema import schema
|
||||
from graphene.contrib.django.views import GraphQLView
|
||||
from graphene_django.views import GraphQLView
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^admin/', admin.site.urls),
|
||||
|
|
|
@ -4,13 +4,14 @@ from django.utils.encoding import force_text
|
|||
from graphene import Enum, List, ID, Boolean, Float, Int, String, Field, NonNull
|
||||
from graphene.types.json import JSONString
|
||||
from graphene.types.datetime import DateTime
|
||||
from graphene.types.json import JSONString
|
||||
from graphene.utils.str_converters import to_const
|
||||
from graphene.relay import Node, ConnectionField
|
||||
# from ...core.types.custom_scalars import DateTime, JSONString
|
||||
from graphene.relay import Node
|
||||
|
||||
from .compat import (ArrayField, HStoreField, JSONField, RangeField,
|
||||
RelatedObject, UUIDField)
|
||||
from .utils import get_related_model, import_single_dispatch
|
||||
from .fields import DjangoConnectionField
|
||||
from .fields import get_connection_field
|
||||
|
||||
singledispatch = import_single_dispatch()
|
||||
|
||||
|
@ -30,8 +31,7 @@ def convert_django_field_with_choices(field, registry=None):
|
|||
meta = field.model._meta
|
||||
name = '{}{}'.format(meta.object_name, field.name.capitalize())
|
||||
graphql_choices = list(convert_choices(choices))
|
||||
from collections import OrderedDict
|
||||
enum = Enum(name, OrderedDict(graphql_choices))
|
||||
enum = Enum(name, list(graphql_choices))
|
||||
return enum(description=field.help_text)
|
||||
return convert_django_field(field, registry)
|
||||
|
||||
|
@ -106,7 +106,7 @@ def convert_field_to_list_or_connection(field, registry=None):
|
|||
return
|
||||
|
||||
if issubclass(_type, Node):
|
||||
return DjangoConnectionField(_type)
|
||||
return get_connection_field(_type)
|
||||
return Field(List(_type))
|
||||
|
||||
|
||||
|
@ -116,8 +116,8 @@ def convert_relatedfield_to_djangomodel(field, registry=None):
|
|||
model = field.model
|
||||
_type = registry.get_type_for_model(model)
|
||||
if issubclass(_type, Node):
|
||||
return DjangoConnectionField(_type)
|
||||
return Field(List(_type))
|
||||
return get_connection_field(_type)
|
||||
return List(_type)
|
||||
|
||||
|
||||
@convert_django_field.register(models.OneToOneField)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from .....core import Boolean, Float, ObjectType, String
|
||||
from graphene import Boolean, Float, ObjectType, String
|
||||
|
||||
|
||||
class DjangoDebugBaseSQL(ObjectType):
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import pytest
|
||||
|
||||
import graphene
|
||||
from graphene.contrib.django import DjangoConnectionField, DjangoNode
|
||||
from graphene.contrib.django.utils import DJANGO_FILTER_INSTALLED
|
||||
from graphene_django import DjangoConnectionField, DjangoNode, DjangoObjectType
|
||||
from graphene_django.utils import DJANGO_FILTER_INSTALLED
|
||||
|
||||
from ...tests.models import Reporter
|
||||
from ..middleware import DjangoDebugMiddleware
|
||||
|
@ -23,7 +23,7 @@ def test_should_query_field():
|
|||
r2 = Reporter(last_name='Griffin')
|
||||
r2.save()
|
||||
|
||||
class ReporterType(DjangoNode):
|
||||
class ReporterType(DjangoNode, DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = Reporter
|
||||
|
@ -69,7 +69,7 @@ def test_should_query_list():
|
|||
r2 = Reporter(last_name='Griffin')
|
||||
r2.save()
|
||||
|
||||
class ReporterType(DjangoNode):
|
||||
class ReporterType(DjangoNode, DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = Reporter
|
||||
|
@ -117,7 +117,7 @@ def test_should_query_connection():
|
|||
r2 = Reporter(last_name='Griffin')
|
||||
r2.save()
|
||||
|
||||
class ReporterType(DjangoNode):
|
||||
class ReporterType(DjangoNode, DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = Reporter
|
||||
|
@ -166,14 +166,14 @@ def test_should_query_connection():
|
|||
@pytest.mark.skipif(not DJANGO_FILTER_INSTALLED,
|
||||
reason="requires django-filter")
|
||||
def test_should_query_connectionfilter():
|
||||
from graphene.contrib.django.filter import DjangoFilterConnectionField
|
||||
from ...filter import DjangoFilterConnectionField
|
||||
|
||||
r1 = Reporter(last_name='ABA')
|
||||
r1.save()
|
||||
r2 = Reporter(last_name='Griffin')
|
||||
r2.save()
|
||||
|
||||
class ReporterType(DjangoNode):
|
||||
class ReporterType(DjangoNode, DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = Reporter
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from ....core.classtypes.objecttype import ObjectType
|
||||
from ....core.types import Field
|
||||
from graphene import ObjectType, List
|
||||
from .sql.types import DjangoDebugBaseSQL
|
||||
|
||||
|
||||
class DjangoDebug(ObjectType):
|
||||
sql = Field(DjangoDebugBaseSQL.List())
|
||||
sql = List(DjangoDebugBaseSQL)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.db.models.query import QuerySet
|
||||
from graphene.relay import ConnectionField
|
||||
from graphql_relay.connection.arrayconnection import connection_from_list_slice
|
||||
from .utils import maybe_queryset
|
||||
from .utils import maybe_queryset, DJANGO_FILTER_INSTALLED
|
||||
|
||||
|
||||
class DjangoConnectionField(ConnectionField):
|
||||
|
@ -39,3 +39,10 @@ class DjangoConnectionField(ConnectionField):
|
|||
connection_type=self.connection,
|
||||
edge_type=self.connection.Edge,
|
||||
)
|
||||
|
||||
|
||||
def get_connection_field(*args, **kwargs):
|
||||
if DJANGO_FILTER_INSTALLED:
|
||||
from .filter.fields import DjangoFilterConnectionField
|
||||
return DjangoFilterConnectionField(*args, **kwargs)
|
||||
return ConnectionField(*args, **kwargs)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import warnings
|
||||
from graphene.contrib.django.utils import DJANGO_FILTER_INSTALLED
|
||||
from ..utils import DJANGO_FILTER_INSTALLED
|
||||
|
||||
if not DJANGO_FILTER_INSTALLED:
|
||||
warnings.warn(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from ..fields import DjangoConnectionField
|
||||
from .utils import get_filtering_args_from_filterset, get_filterset_class
|
||||
|
||||
from graphene.types.argument import to_arguments
|
||||
|
||||
class DjangoFilterConnectionField(DjangoConnectionField):
|
||||
|
||||
|
@ -18,7 +19,7 @@ class DjangoFilterConnectionField(DjangoConnectionField):
|
|||
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(**self.filtering_args)
|
||||
kwargs['args'].update(to_arguments(self.filtering_args))
|
||||
super(DjangoFilterConnectionField, self).__init__(type, *args, **kwargs)
|
||||
|
||||
def get_queryset(self, qs, args, info):
|
||||
|
|
|
@ -5,8 +5,7 @@ from django.utils.text import capfirst
|
|||
from django_filters import Filter, MultipleChoiceFilter
|
||||
from django_filters.filterset import FilterSet, FilterSetMetaclass
|
||||
|
||||
from graphene.contrib.django.forms import (GlobalIDFormField,
|
||||
GlobalIDMultipleChoiceField)
|
||||
from ..forms import GlobalIDFormField, GlobalIDMultipleChoiceField
|
||||
from graphql_relay.node.node import from_global_id
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import django_filters
|
||||
|
||||
from graphene.contrib.django.tests.models import Article, Pet, Reporter
|
||||
from graphene_django.tests.models import Article, Pet, Reporter
|
||||
|
||||
|
||||
class ArticleFilter(django_filters.FilterSet):
|
||||
|
|
|
@ -2,51 +2,58 @@ from datetime import datetime
|
|||
|
||||
import pytest
|
||||
|
||||
from graphene import ObjectType, Schema
|
||||
from graphene.contrib.django import DjangoNode
|
||||
from graphene.contrib.django.forms import (GlobalIDFormField,
|
||||
GlobalIDMultipleChoiceField)
|
||||
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 import ObjectType, Schema, Field
|
||||
from graphene_django import DjangoNode, DjangoObjectType
|
||||
from graphene_django.forms import (GlobalIDFormField,
|
||||
GlobalIDMultipleChoiceField)
|
||||
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.contrib.django.filter import (GlobalIDFilter, DjangoFilterConnectionField,
|
||||
GlobalIDMultipleChoiceFilter)
|
||||
from graphene.contrib.django.filter.tests.filters import ArticleFilter, PetFilter
|
||||
from graphene_django.filter import (GlobalIDFilter, DjangoFilterConnectionField,
|
||||
GlobalIDMultipleChoiceFilter)
|
||||
from graphene_django.filter.tests.filters import ArticleFilter, PetFilter
|
||||
else:
|
||||
pytestmark.append(pytest.mark.skipif(True, reason='django_filters not installed'))
|
||||
|
||||
pytestmark.append(pytest.mark.django_db)
|
||||
|
||||
|
||||
class ArticleNode(DjangoNode):
|
||||
class ArticleNode(DjangoNode, DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = Article
|
||||
|
||||
|
||||
class ReporterNode(DjangoNode):
|
||||
class ReporterNode(DjangoNode, DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = Reporter
|
||||
|
||||
|
||||
class PetNode(DjangoNode):
|
||||
class PetNode(DjangoNode, DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = Pet
|
||||
|
||||
schema = Schema()
|
||||
# schema = Schema()
|
||||
|
||||
|
||||
def get_args(field):
|
||||
if isinstance(field.args, list):
|
||||
return {arg.name: arg for arg in field.args}
|
||||
else:
|
||||
return field.args
|
||||
|
||||
|
||||
def assert_arguments(field, *arguments):
|
||||
ignore = ('after', 'before', 'first', 'last', 'orderBy')
|
||||
args = get_args(field)
|
||||
actual = [
|
||||
name
|
||||
for name in schema.T(field.arguments)
|
||||
for name in args
|
||||
if name not in ignore and not name.startswith('_')
|
||||
]
|
||||
assert set(arguments) == set(actual), \
|
||||
|
@ -57,12 +64,14 @@ def assert_arguments(field, *arguments):
|
|||
|
||||
|
||||
def assert_orderable(field):
|
||||
assert 'orderBy' in schema.T(field.arguments), \
|
||||
args = get_args(field)
|
||||
assert 'orderBy' in args, \
|
||||
'Field cannot be ordered'
|
||||
|
||||
|
||||
def assert_not_orderable(field):
|
||||
assert 'orderBy' not in schema.T(field.arguments), \
|
||||
args = get_args(field)
|
||||
assert 'orderBy' not in args, \
|
||||
'Field can be ordered'
|
||||
|
||||
|
||||
|
@ -122,7 +131,7 @@ def test_filter_shortcut_filterset_extra_meta():
|
|||
|
||||
|
||||
def test_filter_filterset_information_on_meta():
|
||||
class ReporterFilterNode(DjangoNode):
|
||||
class ReporterFilterNode(DjangoNode, DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = Reporter
|
||||
|
@ -135,14 +144,14 @@ def test_filter_filterset_information_on_meta():
|
|||
|
||||
|
||||
def test_filter_filterset_information_on_meta_related():
|
||||
class ReporterFilterNode(DjangoNode):
|
||||
class ReporterFilterNode(DjangoNode, DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = Reporter
|
||||
filter_fields = ['first_name', 'articles']
|
||||
filter_order_by = True
|
||||
|
||||
class ArticleFilterNode(DjangoNode):
|
||||
class ArticleFilterNode(DjangoNode, DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = Article
|
||||
|
@ -152,25 +161,24 @@ def test_filter_filterset_information_on_meta_related():
|
|||
class Query(ObjectType):
|
||||
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
|
||||
all_articles = DjangoFilterConnectionField(ArticleFilterNode)
|
||||
reporter = NodeField(ReporterFilterNode)
|
||||
article = NodeField(ArticleFilterNode)
|
||||
reporter = Field(ReporterFilterNode)
|
||||
article = Field(ArticleFilterNode)
|
||||
|
||||
schema = Schema(query=Query)
|
||||
schema.schema # Trigger the schema loading
|
||||
articles_field = schema.get_type('ReporterFilterNode')._meta.fields_map['articles']
|
||||
articles_field = ReporterFilterNode._meta.graphql_type.get_fields()['articles']
|
||||
assert_arguments(articles_field, 'headline', 'reporter')
|
||||
assert_orderable(articles_field)
|
||||
|
||||
|
||||
def test_filter_filterset_related_results():
|
||||
class ReporterFilterNode(DjangoNode):
|
||||
class ReporterFilterNode(DjangoNode, DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = Reporter
|
||||
filter_fields = ['first_name', 'articles']
|
||||
filter_order_by = True
|
||||
|
||||
class ArticleFilterNode(DjangoNode):
|
||||
class ArticleFilterNode(DjangoNode, DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = Article
|
||||
|
@ -180,8 +188,8 @@ def test_filter_filterset_related_results():
|
|||
class Query(ObjectType):
|
||||
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
|
||||
all_articles = DjangoFilterConnectionField(ArticleFilterNode)
|
||||
reporter = NodeField(ReporterFilterNode)
|
||||
article = NodeField(ArticleFilterNode)
|
||||
reporter = Field(ReporterFilterNode)
|
||||
article = Field(ArticleFilterNode)
|
||||
|
||||
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')
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import six
|
||||
|
||||
from ....core.types import Argument, String
|
||||
from graphene import Argument, String
|
||||
from .filterset import custom_filterset_factory, setup_filterset
|
||||
|
||||
|
||||
|
@ -9,16 +9,16 @@ def get_filtering_args_from_filterset(filterset_class, type):
|
|||
a Graphene Field. These arguments will be available to
|
||||
filter against in the GraphQL
|
||||
"""
|
||||
from graphene.contrib.django.form_converter import convert_form_field
|
||||
from ..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))
|
||||
field_type = 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())
|
||||
args[filterset_class.order_by_field] = String()
|
||||
return args
|
||||
|
||||
|
||||
|
|
|
@ -248,4 +248,4 @@ def test_should_postgres_range_convert_list():
|
|||
from django.contrib.postgres.fields import IntegerRangeField
|
||||
field = assert_conversion(IntegerRangeField, graphene.List)
|
||||
assert isinstance(field.type, graphene.List)
|
||||
# assert isinstance(field.type.of_type, graphene.Int)
|
||||
assert field.type.of_type == get_graphql_type(graphene.Int)
|
||||
|
|
|
@ -8,7 +8,7 @@ from graphene.relay import Node
|
|||
from graphene.relay.node import NodeMeta
|
||||
from .converter import convert_django_field_with_choices
|
||||
from graphene.types.options import Options
|
||||
from .utils import get_model_fields, is_valid_django_model
|
||||
from .utils import get_model_fields, is_valid_django_model, DJANGO_FILTER_INSTALLED
|
||||
from .registry import Registry, get_global_registry
|
||||
from graphene.utils.is_base_type import is_base_type
|
||||
from graphene.utils.copy_fields import copy_fields
|
||||
|
@ -49,8 +49,7 @@ class DjangoObjectTypeMeta(ObjectTypeMeta):
|
|||
if not is_base_type(bases, DjangoObjectTypeMeta):
|
||||
return super_new(cls, name, bases, attrs)
|
||||
|
||||
options = Options(
|
||||
attrs.pop('Meta', None),
|
||||
defaults = dict(
|
||||
name=None,
|
||||
description=None,
|
||||
model=None,
|
||||
|
@ -59,6 +58,19 @@ class DjangoObjectTypeMeta(ObjectTypeMeta):
|
|||
interfaces=(),
|
||||
registry=None
|
||||
)
|
||||
if DJANGO_FILTER_INSTALLED:
|
||||
# In case Django filter is available, then
|
||||
# we allow more attributes in Meta
|
||||
defaults = dict(
|
||||
defaults,
|
||||
filter_fields=(),
|
||||
filter_order_by=(),
|
||||
)
|
||||
|
||||
options = Options(
|
||||
attrs.pop('Meta', None),
|
||||
**defaults
|
||||
)
|
||||
if not options.registry:
|
||||
options.registry = get_global_registry()
|
||||
assert isinstance(options.registry, Registry), 'The attribute registry in {}.Meta needs to be an instance of Registry, received "{}".'.format(name, options.registry)
|
||||
|
@ -77,7 +89,7 @@ class DjangoObjectTypeMeta(ObjectTypeMeta):
|
|||
fields=partial(cls._construct_fields, fields, options),
|
||||
interfaces=tuple(get_interfaces(interfaces + base_interfaces))
|
||||
)
|
||||
options.get_fields = lambda: {}
|
||||
options.get_fields = partial(cls._construct_fields, fields, options)
|
||||
|
||||
if issubclass(cls, DjangoObjectType):
|
||||
options.registry.register(cls)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import re
|
||||
from collections import Iterable
|
||||
import copy
|
||||
from collections import Iterable, OrderedDict
|
||||
|
||||
import six
|
||||
|
||||
|
@ -72,8 +73,15 @@ class Connection(six.with_metaclass(ConnectionMeta, ObjectType)):
|
|||
|
||||
class IterableConnectionField(Field):
|
||||
|
||||
def __init__(self, type, args={}, *other_args, **kwargs):
|
||||
super(IterableConnectionField, self).__init__(type, args=connection_args, *other_args, **kwargs)
|
||||
def __init__(self, type, *other_args, **kwargs):
|
||||
args = kwargs.pop('args', {})
|
||||
if not args:
|
||||
args = connection_args
|
||||
else:
|
||||
args = copy.copy(args)
|
||||
args.update(connection_args)
|
||||
|
||||
super(IterableConnectionField, self).__init__(type, args=args, *other_args, **kwargs)
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
|
|
|
@ -6,6 +6,7 @@ from graphql.type.definition import GraphQLArgument, GraphQLArgumentDefinition
|
|||
from graphql.utils.assert_valid_name import assert_valid_name
|
||||
|
||||
from ..utils.orderedtype import OrderedType
|
||||
from ..utils.str_converters import to_camel_case
|
||||
|
||||
|
||||
class Argument(GraphQLArgument, OrderedType):
|
||||
|
@ -67,7 +68,7 @@ def to_arguments(*args, **extra):
|
|||
raise ValueError('Unknown argument "{}".'.format(default_name))
|
||||
|
||||
arg = Argument.copy_from(arg)
|
||||
arg.name = arg.name or default_name
|
||||
arg.name = arg.name or default_name and to_camel_case(default_name)
|
||||
assert arg.name, 'All arguments must have a name.'
|
||||
assert arg.name not in arguments_names, 'More than one Argument have same name "{}".'.format(arg.name)
|
||||
arguments.append(arg)
|
||||
|
|
Loading…
Reference in New Issue
Block a user