Last active
February 9, 2017 21:28
-
-
Save adamcharnock/78dbc5d8e448b4e5b1d9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import graphene | |
import six | |
from graphene import relay | |
from graphene import ObjectType | |
from graphene.core.types import ObjectTypeMeta | |
from graphene.core.types.scalars import String | |
from graphene.utils.str_converters import to_snake_case | |
schema = graphene.Schema(name='My Schema') | |
class DjangoQueryMeta(ObjectTypeMeta): | |
def __new__(cls, name, bases, attrs): | |
super_new = super(DjangoQueryMeta, cls).__new__ | |
attr_meta = attrs.get('DjangoMeta') | |
if not attr_meta: | |
return super_new(cls, name, bases, attrs) | |
nodes = getattr(attr_meta, 'nodes', []) | |
for node in nodes: | |
# Make the attributes for this node | |
for k, v in cls.make_node_attrs(node).items(): | |
# Set the attribute as the default value | |
attrs.setdefault(k, v) | |
return super_new(cls, name, bases, attrs) | |
@classmethod | |
def make_node_attrs(cls, node): | |
"""Create the attributes related to a given node | |
This will create the following (and return in the form of a dict): | |
- all_{field_name}s -> ConnectionField | |
- {field_name} -> NodeField | |
- resolve_{field_name} -> Callable | |
TODO: Do not mandate field naming in the form 'all_{field_name}s'. | |
The developer should have control of this. | |
""" | |
name = to_snake_case(node.__name__) | |
# Create connection field (with naive pluralisation) | |
connection_field_name = 'all_{}s'.format(name) | |
connection_field = relay.ConnectionField( | |
node, | |
description='Get all {} nodes'.format(node.__name__), | |
args=cls.get_node_args(node) | |
) | |
# Create relay field | |
relay_field_name = name | |
relay_field = relay.NodeField(node) | |
# Create resolve method | |
resolve_method_name = 'resolve_{}'.format(connection_field_name) | |
resolve_method = cls.make_resolve_method(node) | |
return { | |
connection_field_name: connection_field, | |
relay_field_name: relay_field, | |
resolve_method_name: resolve_method, | |
} | |
@classmethod | |
def get_node_args(cls, node): | |
"""Get the args which will become attributes on the new Query class""" | |
args = {} | |
for field in node._meta.fields: | |
# ID field is included automatically, do not include here | |
if field.name == 'id': | |
continue | |
# TODO: Determine correct field type! | |
args[field.attname] = String() | |
return args | |
@classmethod | |
def make_resolve_method(cls, node): | |
"""Factory for creating resolve_{field_name}() methods.""" | |
def resolver(self, args, info): | |
# Crude generalised filtering | |
ignore = ['first', 'last', 'before', 'after'] | |
filter_kwargs = {k: v for k, v in args.items() if k not in ignore} | |
model = node._meta.model | |
return model.objects.filter(**filter_kwargs) | |
return resolver | |
class DjangoQuery(six.with_metaclass(DjangoQueryMeta, ObjectType)): | |
pass | |
class Query(DjangoQuery): | |
class DjangoMeta: | |
# I called this DjangoMeta rather than Meta to avoid | |
# thowing up 'invalid attribute' errors. This may not | |
# even be the right place for this (i.e. could auto-load | |
# nodes from apps, or could be list in settings.py) | |
nodes = [ | |
... your DjangoNodes here... | |
] | |
schema.query = Query |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment