Change mutations to new 2.0 format

This commit is contained in:
Grant McConnaughey 2017-10-02 13:03:20 -05:00
parent 666ddb2ff3
commit 463ce68b16
5 changed files with 128 additions and 235 deletions

View File

@ -4,7 +4,7 @@ from django.core.exceptions import ImproperlyConfigured
import graphene import graphene
from .forms import GlobalIDFormField, GlobalIDMultipleChoiceField from .forms import GlobalIDFormField, GlobalIDMultipleChoiceField
from .utils import import_single_dispatch from ..utils import import_single_dispatch
try: try:
UUIDField = forms.UUIDField UUIDField = forms.UUIDField
@ -40,7 +40,7 @@ def convert_form_field(field):
) )
@convert_form_field.register(forms.BaseTemporalField) @convert_form_field.register(forms.fields.BaseTemporalField)
@convert_form_field.register(forms.CharField) @convert_form_field.register(forms.CharField)
@convert_form_field.register(forms.EmailField) @convert_form_field.register(forms.EmailField)
@convert_form_field.register(forms.SlugField) @convert_form_field.register(forms.SlugField)

View File

@ -1,161 +1,141 @@
from functools import partial from collections import OrderedDict
import six
import graphene import graphene
from graphene import Field, Argument from graphene import Field, InputField
from graphene.types.mutation import MutationMeta from graphene.relay.mutation import ClientIDMutation
from graphene.types.objecttype import ObjectTypeMeta from graphene.types.mutation import MutationOptions
from graphene.types.options import Options from graphene.types.utils import yank_fields_from_attrs
from graphene.types.utils import get_field_as, merge
from graphene.utils.is_base_type import is_base_type
from graphene_django.registry import get_global_registry from graphene_django.registry import get_global_registry
from .converter import convert_form_to_input_type from .converter import convert_form_field
from .types import ErrorType from .types import ErrorType
class FormMutationMeta(MutationMeta): def fields_for_form(form, only_fields, exclude_fields):
def __new__(cls, name, bases, attrs): fields = OrderedDict()
if not is_base_type(bases, FormMutationMeta): for name, field in form.fields.items():
return type.__new__(cls, name, bases, attrs) is_not_in_only = only_fields and name not in only_fields
is_excluded = (
options = Options( name in exclude_fields # or
attrs.pop('Meta', None), # name in already_created_fields
name=name,
description=attrs.pop('__doc__', None),
form_class=None,
input_field_name='input',
local_fields=None,
only_fields=(),
exclude_fields=(),
interfaces=(),
registry=None
) )
if not options.form_class: if is_not_in_only or is_excluded:
raise Exception('Missing form_class') continue
cls = ObjectTypeMeta.__new__( fields[name] = convert_form_field(field)
cls, name, bases, dict(attrs, _meta=options) return fields
)
options.fields = merge(
options.interface_fields, options.base_fields, options.local_fields,
{'errors': get_field_as(cls.errors, Field)}
)
cls.Input = convert_form_to_input_type(options.form_class)
field_kwargs = {options.input_field_name: Argument(cls.Input, required=True)}
cls.Field = partial(
Field,
cls,
resolver=cls.mutate,
**field_kwargs
)
return cls
class BaseFormMutation(graphene.Mutation): class BaseFormMutation(ClientIDMutation):
class Meta:
abstract = True
@classmethod @classmethod
def mutate(cls, root, args, context, info): def mutate_and_get_payload(cls, root, info, **input):
form = cls.get_form(root, args, context, info) form = cls._meta.form_class(data=input)
if form.is_valid(): if form.is_valid():
return cls.form_valid(form, info) return cls.perform_mutate(form, info)
else: else:
return cls.form_invalid(form, info)
@classmethod
def form_valid(cls, form, info):
form.save()
return cls(errors=[])
@classmethod
def form_invalid(cls, form, info):
errors = [ errors = [
ErrorType(field=key, messages=value) ErrorType(field=key, messages=value)
for key, value in form.errors.items() for key, value in form.errors.items()
] ]
return cls(errors=errors) return cls(errors=errors)
@classmethod
def get_form(cls, root, args, context, info):
form_data = args.get(cls._meta.input_field_name)
kwargs = cls.get_form_kwargs(root, args, context, info)
return cls._meta.form_class(data=form_data, **kwargs)
@classmethod class FormMutationOptions(MutationOptions):
def get_form_kwargs(cls, root, args, context, info): form_class = None
return {}
class FormMutation(six.with_metaclass(FormMutationMeta, BaseFormMutation)): class FormMutation(BaseFormMutation):
class Meta:
abstract = True
errors = graphene.List(ErrorType) errors = graphene.List(ErrorType)
@classmethod
def __init_subclass_with_meta__(cls, form_class=None,
only_fields=(), exclude_fields=(), **options):
class ModelFormMutationMeta(MutationMeta): if not form_class:
def __new__(cls, name, bases, attrs): raise Exception('form_class is required for FormMutation')
if not is_base_type(bases, ModelFormMutationMeta):
return type.__new__(cls, name, bases, attrs)
options = Options( form = form_class()
attrs.pop('Meta', None), input_fields = fields_for_form(form, only_fields, exclude_fields)
name=name, output_fields = fields_for_form(form, only_fields, exclude_fields)
description=attrs.pop('__doc__', None),
form_class=None, _meta = FormMutationOptions(cls)
input_field_name='input', _meta.form_class = form_class
return_field_name=None, _meta.fields = yank_fields_from_attrs(
model=None, output_fields,
local_fields=None, _as=Field,
only_fields=(),
exclude_fields=(),
interfaces=(),
registry=None
) )
if not options.form_class: input_fields = yank_fields_from_attrs(
raise Exception('Missing form_class') input_fields,
_as=InputField,
cls = ObjectTypeMeta.__new__(
cls, name, bases, dict(attrs, _meta=options)
) )
super(FormMutation, cls).__init_subclass_with_meta__(_meta=_meta, input_fields=input_fields, **options)
options.fields = merge( @classmethod
options.interface_fields, options.base_fields, options.local_fields, def perform_mutate(cls, form, info):
{'errors': get_field_as(cls.errors, Field)} form.save()
) return cls(errors=None)
cls.Input = convert_form_to_input_type(options.form_class)
field_kwargs = {options.input_field_name: Argument(cls.Input, required=True)} class ModelFormMutationOptions(FormMutationOptions):
cls.Field = partial( model = None
Field, return_field_name = None
cls,
resolver=cls.mutate,
**field_kwargs
)
cls.model = options.model or options.form_class.Meta.model
cls.return_field_name = cls._meta.return_field_name or cls.model._meta.model_name class ModelFormMutation(BaseFormMutation):
class Meta:
abstract = True
errors = graphene.List(ErrorType)
@classmethod
def __init_subclass_with_meta__(cls, form_class=None, model=None, return_field_name=None,
only_fields=(), exclude_fields=(), **options):
if not form_class:
raise Exception('form_class is required for ModelFormMutation')
if not model:
model = form_class._meta.model
if not model:
raise Exception('model is required for ModelFormMutation')
form = form_class()
input_fields = fields_for_form(form, only_fields, exclude_fields)
registry = get_global_registry() registry = get_global_registry()
model_type = registry.get_type_for_model(cls.model) model_type = registry.get_type_for_model(model)
return_field_name = return_field_name or model._meta.model_name
output_fields = OrderedDict()
output_fields[return_field_name] = graphene.Field(model_type)
options.fields[cls.return_field_name] = graphene.Field(model_type) _meta = ModelFormMutationOptions(cls)
_meta.form_class = form_class
_meta.model = model
_meta.return_field_name = return_field_name
_meta.fields = yank_fields_from_attrs(
output_fields,
_as=Field,
)
return cls input_fields = yank_fields_from_attrs(
input_fields,
_as=InputField,
class ModelFormMutation(six.with_metaclass(ModelFormMutationMeta, BaseFormMutation)): )
super(ModelFormMutation, cls).__init_subclass_with_meta__(_meta=_meta, input_fields=input_fields, **options)
errors = graphene.List(ErrorType)
@classmethod @classmethod
def form_valid(cls, form, info): def perform_mutate(cls, form, info):
obj = form.save() obj = form.save()
kwargs = {cls.return_field_name: obj} kwargs = {cls._meta.return_field_name: obj}
return cls(errors=[], **kwargs) return cls(errors=None, **kwargs)

View File

@ -5,7 +5,6 @@ import graphene
from graphene import ID, List, NonNull from graphene import ID, List, NonNull
from ..converter import convert_form_field from ..converter import convert_form_field
from .models import Reporter
def assert_conversion(django_field, graphene_field, *args): def assert_conversion(django_field, graphene_field, *args):
@ -24,15 +23,15 @@ def test_should_unknown_django_field_raise_exception():
def test_should_date_convert_string(): def test_should_date_convert_string():
assert_conversion(forms.DateField, graphene.String) assert_conversion(forms.DateField, graphene.types.datetime.DateTime)
def test_should_time_convert_string(): def test_should_time_convert_string():
assert_conversion(forms.TimeField, graphene.String) assert_conversion(forms.TimeField, graphene.types.datetime.Time)
def test_should_date_time_convert_string(): def test_should_date_time_convert_string():
assert_conversion(forms.DateTimeField, graphene.String) assert_conversion(forms.DateTimeField, graphene.types.datetime.DateTime)
def test_should_char_convert_string(): def test_should_char_convert_string():
@ -91,13 +90,13 @@ def test_should_decimal_convert_float():
def test_should_multiple_choice_convert_connectionorlist(): def test_should_multiple_choice_convert_connectionorlist():
field = forms.ModelMultipleChoiceField(Reporter.objects.all()) field = forms.ModelMultipleChoiceField(queryset=None)
graphene_type = convert_form_field(field) graphene_type = convert_form_field(field)
assert isinstance(graphene_type, List) assert isinstance(graphene_type, List)
assert graphene_type.of_type == ID assert graphene_type.of_type == ID
def test_should_manytoone_convert_connectionorlist(): def test_should_manytoone_convert_connectionorlist():
field = forms.ModelChoiceField(Reporter.objects.all()) field = forms.ModelChoiceField(queryset=None)
graphene_type = convert_form_field(field) graphene_type = convert_form_field(field)
assert isinstance(graphene_type, graphene.ID) assert isinstance(graphene_type, graphene.ID)

View File

@ -1,98 +0,0 @@
import copy
from django import forms
from py.test import raises
import graphene
from ..converter import convert_form_field
def _get_type(form_field, **kwargs):
# prevents the following error:
# AssertionError: The `source` argument is not meaningful when applied to a `child=` field.
# Remove `source=` from the field declaration.
# since we are reusing the same child in when testing the required attribute
if 'child' in kwargs:
kwargs['child'] = copy.deepcopy(kwargs['child'])
field = form_field(**kwargs)
return convert_form_field(field)
def assert_conversion(form_field, graphene_field, **kwargs):
graphene_type = _get_type(form_field, help_text='Custom Help Text', **kwargs)
assert isinstance(graphene_type, graphene_field)
graphene_type_required = _get_type(
form_field, help_text='Custom Help Text', required=True, **kwargs
)
assert isinstance(graphene_type_required, graphene_field)
return graphene_type
def test_should_unknown_form_field_raise_exception():
with raises(Exception) as excinfo:
convert_form_field(None)
assert 'Don\'t know how to convert the form field' in str(excinfo.value)
def test_should_charfield_convert_string():
assert_conversion(forms.CharField, graphene.String)
def test_should_timefield_convert_time():
assert_conversion(forms.TimeField, graphene.types.datetime.Time)
def test_should_email_convert_string():
assert_conversion(forms.EmailField, graphene.String)
def test_should_slug_convert_string():
assert_conversion(forms.SlugField, graphene.String)
def test_should_url_convert_string():
assert_conversion(forms.URLField, graphene.String)
def test_should_choicefield_convert_string():
assert_conversion(forms.ChoiceField, graphene.String, choices=[])
def test_should_regexfield_convert_string():
assert_conversion(forms.RegexField, graphene.String, regex='[0-9]+')
def test_should_uuidfield_convert_string():
assert_conversion(forms.UUIDField, graphene.String)
def test_should_integer_convert_int():
assert_conversion(forms.IntegerField, graphene.Int)
def test_should_boolean_convert_boolean():
assert_conversion(forms.BooleanField, graphene.Boolean)
def test_should_float_convert_float():
assert_conversion(forms.FloatField, graphene.Float)
def test_should_decimal_convert_float():
assert_conversion(forms.DecimalField, graphene.Float, max_digits=4, decimal_places=2)
def test_should_filepath_convert_string():
assert_conversion(forms.FilePathField, graphene.String, path='/')
def test_should_multiplechoicefield_convert_to_list_of_string():
field = assert_conversion(forms.MultipleChoiceField, graphene.List, choices=[1, 2, 3])
assert field.of_type == graphene.String

View File

@ -2,7 +2,7 @@ from django import forms
from django.test import TestCase from django.test import TestCase
from py.test import raises from py.test import raises
from graphene_django.tests.models import Pet from graphene_django.tests.models import Pet, Film
from ..mutation import FormMutation, ModelFormMutation from ..mutation import FormMutation, ModelFormMutation
@ -22,10 +22,10 @@ def test_needs_form_class():
class MyMutation(FormMutation): class MyMutation(FormMutation):
pass pass
assert exc.value.args[0] == 'Missing form_class' assert exc.value.args[0] == 'form_class is required for FormMutation'
def test_has_fields(): def test_has_output_fields():
class MyMutation(FormMutation): class MyMutation(FormMutation):
class Meta: class Meta:
form_class = MyForm form_class = MyForm
@ -43,20 +43,32 @@ def test_has_input_fields():
class ModelFormMutationTests(TestCase): class ModelFormMutationTests(TestCase):
def test_model_form_mutation(self): def test_default_meta_fields(self):
class PetMutation(ModelFormMutation): class PetMutation(ModelFormMutation):
class Meta: class Meta:
form_class = PetForm form_class = PetForm
self.assertEqual(PetMutation.model, Pet) self.assertEqual(PetMutation._meta.model, Pet)
self.assertEqual(PetMutation.return_field_name, 'pet') self.assertEqual(PetMutation._meta.return_field_name, 'pet')
self.assertIn('pet', PetMutation._meta.fields)
def test_custom_return_field_name(self):
class PetMutation(ModelFormMutation):
class Meta:
form_class = PetForm
model = Film
return_field_name = 'animal'
self.assertEqual(PetMutation._meta.model, Film)
self.assertEqual(PetMutation._meta.return_field_name, 'animal')
self.assertIn('animal', PetMutation._meta.fields)
def test_model_form_mutation_mutate(self): def test_model_form_mutation_mutate(self):
class PetMutation(ModelFormMutation): class PetMutation(ModelFormMutation):
class Meta: class Meta:
form_class = PetForm form_class = PetForm
PetMutation.mutate(None, {'input': {'name': 'Fluffy'}}, None, None) PetMutation.mutate_and_get_payload(None, None, name='Fluffy')
self.assertEqual(Pet.objects.count(), 1) self.assertEqual(Pet.objects.count(), 1)
pet = Pet.objects.get() pet = Pet.objects.get()