mirror of
https://github.com/graphql-python/graphene-django.git
synced 2024-11-10 19:57:15 +03:00
Change mutations to new 2.0 format
This commit is contained in:
parent
666ddb2ff3
commit
463ce68b16
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue
Block a user