diff --git a/graphene_django/input_types.py b/graphene_django/input_types.py index 7277959..0835732 100644 --- a/graphene_django/input_types.py +++ b/graphene_django/input_types.py @@ -5,56 +5,3 @@ from graphene.types.utils import merge from graphene.utils.is_base_type import is_base_type -def convert_fields(model, only_fields, exclude_fields): - model_fields = utils.get_model_fields(model=model) - fields = collections.OrderedDict() - - for name, field in model_fields: - is_not_in_only = only_fields and name not in only_fields - is_already_created = name in model_fields - is_excluded = name in exclude_fields or is_already_created - # https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.ForeignKey.related_query_name - is_no_backref = str(name).endswith('+') - if is_not_in_only or is_excluded or is_no_backref: - # We skip this field if we specify only_fields and is not - # in there. Or when we exclude this field in exclude_fields. - # Or when there is no back reference. - continue - converted = converter.convert_django_field(field, None) - if not converted: - continue - - fields[name] = converted - print(fields) - return fields - - -class DjangoModelInputMeta(type): - - @staticmethod - def __new__(cls, name, bases, attrs): - # We'll get called also for non-user classes like DjangoModelInput. Only - # kick in when called for a sub-class. - if not is_base_type(bases, DjangoModelInputMeta): - return type.__new__(cls, name, bases, attrs) - - # Pop Meta info. Must be removed from class, otherwise graphene will - # complain. - meta = attrs.pop('Meta') - if not hasattr(meta, 'exclude_fields'): - setattr(meta, 'exclude_fields', ()) - if not hasattr(meta, 'only_fields'): - setattr(meta, 'only_fields', ()) - fields = convert_fields(model=meta.model, only_fields=meta.only_fields, exclude_fields=meta.exclude_fields) - attrs = merge(attrs, fields) - - return type.__new__(cls, name, bases, attrs) - - -class DjangoModelInput(metaclass=DjangoModelInputMeta): - """ - Derive a mutation's Input class from this and define a meta class with - `model` and `only_fields` members. This will populate the input class - with the converted django members. - """ - pass \ No newline at end of file diff --git a/graphene_django/tests/test_input_type.py b/graphene_django/tests/test_input_type.py deleted file mode 100644 index ab4a10e..0000000 --- a/graphene_django/tests/test_input_type.py +++ /dev/null @@ -1,96 +0,0 @@ -from mock import patch - -from graphene import ObjectType, Schema, Mutation, String - -from .. import registry -from ..input_types import DjangoModelInput - -from .models import Reporter as ReporterModel - - -def test_mutation_execution_with_exclude_fields(): - registry.reset_global_registry() - - class CreateReporter(Mutation): - - first_name = String() - last_name = String() - email = String() - - class Input(DjangoModelInput): - - class Meta: - model = ReporterModel - exclude_fields = ('id', 'pets', 'a_choice', 'films', 'articles') - - def mutate(self, args, context, info): - first_name = args.get('first_name') - last_name = args.get('last_name') - email = args.get('email') - return CreateReporter(first_name=first_name, last_name=last_name, email=email) - - class MyMutation(ObjectType): - reporter_input = CreateReporter.Field() - - class Query(ObjectType): - a = String() - - schema = Schema(query=Query, mutation=MyMutation) - result = schema.execute(''' mutation mymutation { - reporterInput(firstName:"Peter", lastName: "test", email: "test@test.com") { - firstName - lastName - email - } - } - ''') - assert not result.errors - assert result.data == { - 'reporterInput': { - 'firstName': 'Peter', - 'lastName': 'test', - 'email': "test@test.com" - } - } - - -def test_mutation_execution(): - registry.reset_global_registry() - - class ReporterInput(Mutation): - - first_name = String() - last_name = String() - - class Input(DjangoModelInput): - - class Meta: - model = ReporterModel - only_fields = ('first_name', 'last_name') - - def mutate(self, args, context, info): - first_name = args.get('first_name') - last_name = args.get('last_name') - return ReporterInput(first_name=first_name, last_name=last_name) - - class MyMutation(ObjectType): - reporter_input = ReporterInput.Field() - - class Query(ObjectType): - a = String() - - schema = Schema(query=Query, mutation=MyMutation) - result = schema.execute(''' mutation mymutation { - reporterInput(firstName:"Peter", lastName: "test") { - firstName - lastName - } - } - ''') - assert not result.errors - assert result.data == { - 'reporterInput': { - 'firstName': 'Peter', - 'lastName': 'test', - } - } diff --git a/graphene_django/tests/test_types.py b/graphene_django/tests/test_types.py index 0ae12c0..70daf52 100644 --- a/graphene_django/tests/test_types.py +++ b/graphene_django/tests/test_types.py @@ -1,10 +1,11 @@ from mock import patch -from graphene import Interface, ObjectType, Schema +from graphene import Interface, ObjectType, Schema, Mutation, String from graphene.relay import Node +from mock import patch from .. import registry -from ..types import DjangoObjectType +from ..types import DjangoObjectType, DjangoModelInput from .models import Article as ArticleModel from .models import Reporter as ReporterModel @@ -163,3 +164,91 @@ def test_django_objecttype_exclude_fields(): fields = list(Reporter._meta.fields.keys()) assert 'email' not in fields + + +def test_mutation_execution_with_exclude_fields(): + registry.reset_global_registry() + + class CreateReporter(Mutation): + + first_name = String() + last_name = String() + email = String() + + class Input(DjangoModelInput): + + class Meta: + model = ReporterModel + exclude_fields = ('id', 'pets', 'a_choice', 'films', 'articles') + + def mutate(self, args, context, info): + first_name = args.get('first_name') + last_name = args.get('last_name') + email = args.get('email') + return CreateReporter(first_name=first_name, last_name=last_name, email=email) + + class MyMutation(ObjectType): + reporter_input = CreateReporter.Field() + + class Query(ObjectType): + a = String() + + schema = Schema(query=Query, mutation=MyMutation) + result = schema.execute(''' mutation mymutation { + reporterInput(firstName:"Peter", lastName: "test", email: "test@test.com") { + firstName + lastName + email + } + } + ''') + assert not result.errors + assert result.data == { + 'reporterInput': { + 'firstName': 'Peter', + 'lastName': 'test', + 'email': "test@test.com" + } + } + + +def test_mutation_execution(): + registry.reset_global_registry() + + class ReporterInput(Mutation): + + first_name = String() + last_name = String() + + class Input(DjangoModelInput): + + class Meta: + model = ReporterModel + only_fields = ('first_name', 'last_name') + + def mutate(self, args, context, info): + first_name = args.get('first_name') + last_name = args.get('last_name') + return ReporterInput(first_name=first_name, last_name=last_name) + + class MyMutation(ObjectType): + reporter_input = ReporterInput.Field() + + class Query(ObjectType): + a = String() + + schema = Schema(query=Query, mutation=MyMutation) + result = schema.execute(''' mutation mymutation { + reporterInput(firstName:"Peter", lastName: "test") { + firstName + lastName + } + } + ''') + assert not result.errors + assert result.data == { + 'reporterInput': { + 'firstName': 'Peter', + 'lastName': 'test', + } + } diff --git a/graphene_django/types.py b/graphene_django/types.py index bb0a2f1..6a1e2a8 100644 --- a/graphene_django/types.py +++ b/graphene_django/types.py @@ -122,3 +122,57 @@ class DjangoObjectType(six.with_metaclass(DjangoObjectTypeMeta, ObjectType)): return cls._meta.model.objects.get(pk=id) except cls._meta.model.DoesNotExist: return None + + +def convert_fields(model, only_fields, exclude_fields): + model_fields = get_model_fields(model=model) + fields = OrderedDict() + + for name, field in model_fields: + is_not_in_only = only_fields and name not in only_fields + is_already_created = name in model_fields + is_excluded = name in exclude_fields or is_already_created + is_no_backref = str(name).endswith('+') + if is_not_in_only or is_excluded or is_no_backref: + # We skip this field if we specify only_fields and is not + # in there. Or when we exclude this field in exclude_fields. + # Or when there is no back reference. + continue + converted = convert_django_field_with_choices(field, None) + if not converted: + continue + + fields[name] = converted + print(fields) + return fields + + +class DjangoModelInputMeta(type): + + @staticmethod + def __new__(cls, name, bases, attrs): + # We'll get called also for non-user classes like DjangoModelInput. Only + # kick in when called for a sub-class. + if not is_base_type(bases, DjangoModelInputMeta): + return type.__new__(cls, name, bases, attrs) + + # Pop Meta info. Must be removed from class, otherwise graphene will + # complain. + meta = attrs.pop('Meta') + if not hasattr(meta, 'exclude_fields'): + setattr(meta, 'exclude_fields', ()) + if not hasattr(meta, 'only_fields'): + setattr(meta, 'only_fields', ()) + fields = convert_fields(model=meta.model, only_fields=meta.only_fields, exclude_fields=meta.exclude_fields) + attrs = merge(attrs, fields) + + return type.__new__(cls, name, bases, attrs) + + +class DjangoModelInput(six.with_metaclass(DjangoModelInputMeta)): + """ + Derive a mutation's Input class from this and define a meta class with + `model` and `only_fields` members. This will populate the input class + with the converted django members. + """ + pass \ No newline at end of file