diff --git a/graphene_django/tests/test_types.py b/graphene_django/tests/test_types.py index 0ae12c02f..70daf522d 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 @@ class Meta: 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 bb0a2f1f1..4f5e82639 100644 --- a/graphene_django/types.py +++ b/graphene_django/types.py @@ -122,3 +122,56 @@ def get_node(cls, id, context, info): 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` and 'exclude_field' members. This will populate the input class + with the converted django members. + """ + pass