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
from .forms import GlobalIDFormField, GlobalIDMultipleChoiceField
from .utils import import_single_dispatch
from ..utils import import_single_dispatch
try:
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.EmailField)
@convert_form_field.register(forms.SlugField)

View File

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

View File

@ -5,7 +5,6 @@ import graphene
from graphene import ID, List, NonNull
from ..converter import convert_form_field
from .models import Reporter
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():
assert_conversion(forms.DateField, graphene.String)
assert_conversion(forms.DateField, graphene.types.datetime.DateTime)
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():
assert_conversion(forms.DateTimeField, graphene.String)
assert_conversion(forms.DateTimeField, graphene.types.datetime.DateTime)
def test_should_char_convert_string():
@ -91,13 +90,13 @@ def test_should_decimal_convert_float():
def test_should_multiple_choice_convert_connectionorlist():
field = forms.ModelMultipleChoiceField(Reporter.objects.all())
field = forms.ModelMultipleChoiceField(queryset=None)
graphene_type = convert_form_field(field)
assert isinstance(graphene_type, List)
assert graphene_type.of_type == ID
def test_should_manytoone_convert_connectionorlist():
field = forms.ModelChoiceField(Reporter.objects.all())
field = forms.ModelChoiceField(queryset=None)
graphene_type = convert_form_field(field)
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 py.test import raises
from graphene_django.tests.models import Pet
from graphene_django.tests.models import Pet, Film
from ..mutation import FormMutation, ModelFormMutation
@ -22,10 +22,10 @@ def test_needs_form_class():
class MyMutation(FormMutation):
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 Meta:
form_class = MyForm
@ -43,20 +43,32 @@ def test_has_input_fields():
class ModelFormMutationTests(TestCase):
def test_model_form_mutation(self):
def test_default_meta_fields(self):
class PetMutation(ModelFormMutation):
class Meta:
form_class = PetForm
self.assertEqual(PetMutation.model, Pet)
self.assertEqual(PetMutation.return_field_name, 'pet')
self.assertEqual(PetMutation._meta.model, 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):
class PetMutation(ModelFormMutation):
class Meta:
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)
pet = Pet.objects.get()