From 76147d7c26f83a3c6c9ad2230c44e2ea9f33ee1b Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Mon, 28 Sep 2015 01:51:51 -0700 Subject: [PATCH] Improved Django model conversion --- graphene/contrib/django/converter.py | 28 +++++++++++++++++++++------- graphene/contrib/django/fields.py | 23 +++++++++++++++++++++++ graphene/contrib/django/types.py | 13 +++++++++++-- graphene/core/fields.py | 4 ++-- graphene/core/options.py | 8 ++++++++ graphene/core/schema.py | 4 ++++ tests/contrib_django/test_schema.py | 8 ++++++++ 7 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 graphene/contrib/django/fields.py diff --git a/graphene/contrib/django/converter.py b/graphene/contrib/django/converter.py index 590d9064..6611d356 100644 --- a/graphene/contrib/django/converter.py +++ b/graphene/contrib/django/converter.py @@ -7,34 +7,48 @@ from graphene.core.fields import ( IntField, BooleanField, FloatField, + ListField ) +from graphene.contrib.django.fields import DjangoModelField @singledispatch -def convert_django_field(field): - raise Exception("Don't know how to convert the Django field %s"%field) +def convert_django_field(field, cls): + raise Exception("Don't know how to convert the Django field %s (%s)" % (field, field.__class__)) +@convert_django_field.register(models.DateField) @convert_django_field.register(models.CharField) -def _(field): +@convert_django_field.register(models.TextField) +def _(field, cls): return StringField(description=field.help_text) @convert_django_field.register(models.AutoField) -def _(field): +def _(field, cls): return IDField(description=field.help_text) @convert_django_field.register(models.BigIntegerField) @convert_django_field.register(models.IntegerField) -def _(field): +def _(field, cls): return IntField(description=field.help_text) @convert_django_field.register(models.BooleanField) -def _(field): +def _(field, cls): return BooleanField(description=field.help_text) @convert_django_field.register(models.FloatField) -def _(field): +def _(field, cls): return FloatField(description=field.help_text) + + +@convert_django_field.register(models.ManyToOneRel) +def _(field, cls): + return ListField(DjangoModelField(field.related_model)) + + +@convert_django_field.register(models.ForeignKey) +def _(field, cls): + return DjangoModelField(field.related_model) diff --git a/graphene/contrib/django/fields.py b/graphene/contrib/django/fields.py new file mode 100644 index 00000000..683f245a --- /dev/null +++ b/graphene/contrib/django/fields.py @@ -0,0 +1,23 @@ +from graphene.core.fields import Field +from graphene.utils import cached_property + +from graphene.env import get_global_schema + + +def get_type_for_model(schema, model): + schema = schema or get_global_schema() + types = schema.types.values() + for _type in types: + type_model = getattr(_type._meta, 'model', None) + if model == type_model: + return _type._meta.type + + +class DjangoModelField(Field): + def __init__(self, model): + super(DjangoModelField, self).__init__(None) + self.model = model + + @cached_property + def type(self): + return get_type_for_model(self.schema, self.model) diff --git a/graphene/contrib/django/types.py b/graphene/contrib/django/types.py index daa7c6b7..dbde3aa9 100644 --- a/graphene/contrib/django/types.py +++ b/graphene/contrib/django/types.py @@ -1,4 +1,5 @@ import six +from django.db import models from graphene.core.types import ObjectTypeMeta, ObjectType from graphene.contrib.django.options import DjangoOptions @@ -6,6 +7,13 @@ from graphene.contrib.django.converter import convert_django_field from graphene.relay import Node +def get_reverse_fields(model): + for name, attr in model.__dict__.items(): + related = getattr(attr, 'related', None) + if isinstance(related, models.ManyToOneRel): + yield related + + class DjangoObjectTypeMeta(ObjectTypeMeta): options_cls = DjangoOptions def add_extra_fields(cls): @@ -14,10 +22,11 @@ class DjangoObjectTypeMeta(ObjectTypeMeta): only_fields = cls._meta.only_fields # print cls._meta.model._meta._get_fields(forward=False, reverse=True, include_hidden=True) - for field in cls._meta.model._meta.fields: + reverse_fields = tuple(get_reverse_fields(cls._meta.model)) + for field in cls._meta.model._meta.fields + reverse_fields: if only_fields and field.name not in only_fields: continue - converted_field = convert_django_field(field) + converted_field = convert_django_field(field, cls) cls.add_to_class(field.name, converted_field) diff --git a/graphene/core/fields.py b/graphene/core/fields.py index 12258c6a..254c2f02 100644 --- a/graphene/core/fields.py +++ b/graphene/core/fields.py @@ -76,8 +76,8 @@ class Field(object): @cached_property def field(self): - if not self.field_type: - raise Exception('Must specify a field GraphQL type for the field %s'%self.field_name) + # if not self.field_type: + # raise Exception('Must specify a field GraphQL type for the field %s'%self.field_name) if not self.object_type: raise Exception('Field could not be constructed in a non graphene.Type or graphene.Interface') diff --git a/graphene/core/options.py b/graphene/core/options.py index c0d48fc1..101c8a22 100644 --- a/graphene/core/options.py +++ b/graphene/core/options.py @@ -16,6 +16,14 @@ class Options(object): self.parents = [] self.valid_attrs = DEFAULT_NAMES + # @property + # def schema(self): + # return self._schema or get_global_schema() + + # @schema.setter + # def schema(self, schema): + # self._schema = schema + def contribute_to_class(self, cls, name): cls._meta = self self.parent = cls diff --git a/graphene/core/schema.py b/graphene/core/schema.py index 75f11eae..63cf2318 100644 --- a/graphene/core/schema.py +++ b/graphene/core/schema.py @@ -42,6 +42,10 @@ class Schema(object): raise Exception('Type %s not found in %r' % (type_name, self)) return self._types[type_name] + @property + def types(self): + return self._types + def execute(self, request='', root=None, vars=None, operation_name=None): root = root or object() return graphql( diff --git a/tests/contrib_django/test_schema.py b/tests/contrib_django/test_schema.py index cbfea539..5dbf093d 100644 --- a/tests/contrib_django/test_schema.py +++ b/tests/contrib_django/test_schema.py @@ -74,6 +74,14 @@ def test_should_node(): def get_node(cls, id): return ReporterNodeType(Reporter(id=2, first_name='Cookie Monster')) + class ArticleNodeType(DjangoNode): + class Meta: + model = Article + + @classmethod + def get_node(cls, id): + return ArticleNodeType(None) + class Query(graphene.ObjectType): node = relay.NodeField() reporter = graphene.Field(ReporterNodeType)