mirror of
https://github.com/graphql-python/graphene-django.git
synced 2025-01-31 11:48:38 +03:00
Merge pull request #450 from graphql-python/form_mutations
Form mutations
This commit is contained in:
commit
4e7b269b76
68
docs/form-mutations.rst
Normal file
68
docs/form-mutations.rst
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
Integration with Django forms
|
||||||
|
=============================
|
||||||
|
|
||||||
|
Graphene-Django comes with mutation classes that will convert the fields on Django forms into inputs on a mutation.
|
||||||
|
*Note: the API is experimental and will likely change in the future.*
|
||||||
|
|
||||||
|
FormMutation
|
||||||
|
------------
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
class MyForm(forms.Form):
|
||||||
|
name = forms.CharField()
|
||||||
|
|
||||||
|
class MyMutation(FormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = MyForm
|
||||||
|
|
||||||
|
``MyMutation`` will automatically receive an ``input`` argument. This argument should be a ``dict`` where the key is ``name`` and the value is a string.
|
||||||
|
|
||||||
|
ModelFormMutation
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
``ModelFormMutation`` will pull the fields from a ``ModelForm``.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
class Pet(models.Model):
|
||||||
|
name = models.CharField()
|
||||||
|
|
||||||
|
class PetForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Pet
|
||||||
|
fields = ('name',)
|
||||||
|
|
||||||
|
# This will get returned when the mutation completes successfully
|
||||||
|
class PetType(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = Pet
|
||||||
|
|
||||||
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = PetForm
|
||||||
|
|
||||||
|
``PetMutation`` will grab the fields from ``PetForm`` and turn them into inputs. If the form is valid then the mutation
|
||||||
|
will lookup the ``DjangoObjectType`` for the ``Pet`` model and return that under the key ``pet``. Otherwise it will
|
||||||
|
return a list of errors.
|
||||||
|
|
||||||
|
You can change the input name (default is ``input``) and the return field name (default is the model name lowercase).
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = PetForm
|
||||||
|
input_field_name = 'data'
|
||||||
|
return_field_name = 'my_pet'
|
||||||
|
|
||||||
|
Form validation
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Form mutations will call ``is_valid()`` on your forms.
|
||||||
|
|
||||||
|
If the form is valid then ``form_valid(form, info)`` is called on the mutation. Override this method to change how
|
||||||
|
the form is saved or to return a different Graphene object type.
|
||||||
|
|
||||||
|
If the form is *not* valid then a list of errors will be returned. These errors have two fields: ``field``, a string
|
||||||
|
containing the name of the invalid form field, and ``messages``, a list of strings with the validation messages.
|
|
@ -12,4 +12,5 @@ Contents:
|
||||||
authorization
|
authorization
|
||||||
debug
|
debug
|
||||||
rest-framework
|
rest-framework
|
||||||
|
form-mutations
|
||||||
introspection
|
introspection
|
||||||
|
|
|
@ -8,7 +8,7 @@ def get_filtering_args_from_filterset(filterset_class, type):
|
||||||
a Graphene Field. These arguments will be available to
|
a Graphene Field. These arguments will be available to
|
||||||
filter against in the GraphQL
|
filter against in the GraphQL
|
||||||
"""
|
"""
|
||||||
from ..form_converter import convert_form_field
|
from ..forms.converter import convert_form_field
|
||||||
|
|
||||||
args = {}
|
args = {}
|
||||||
for name, filter_field in six.iteritems(filterset_class.base_filters):
|
for name, filter_field in six.iteritems(filterset_class.base_filters):
|
||||||
|
|
1
graphene_django/forms/__init__.py
Normal file
1
graphene_django/forms/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
from .forms import GlobalIDFormField, GlobalIDMultipleChoiceField # noqa
|
|
@ -1,25 +1,25 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.forms.fields import BaseTemporalField
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
from graphene import ID, Boolean, Float, Int, List, String, UUID
|
from graphene import ID, Boolean, Float, Int, List, String, UUID, Date, DateTime, Time
|
||||||
from graphene.types.datetime import Date, DateTime, Time
|
|
||||||
|
|
||||||
from .forms import GlobalIDFormField, GlobalIDMultipleChoiceField
|
from .forms import GlobalIDFormField, GlobalIDMultipleChoiceField
|
||||||
from .utils import import_single_dispatch
|
from ..utils import import_single_dispatch
|
||||||
|
|
||||||
|
|
||||||
singledispatch = import_single_dispatch()
|
singledispatch = import_single_dispatch()
|
||||||
|
|
||||||
|
|
||||||
@singledispatch
|
@singledispatch
|
||||||
def convert_form_field(field):
|
def convert_form_field(field):
|
||||||
raise Exception(
|
raise ImproperlyConfigured(
|
||||||
"Don't know how to convert the Django form field %s (%s) "
|
"Don't know how to convert the Django form field %s (%s) "
|
||||||
"to Graphene type" %
|
"to Graphene type" %
|
||||||
(field, field.__class__)
|
(field, field.__class__)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@convert_form_field.register(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)
|
193
graphene_django/forms/mutation.py
Normal file
193
graphene_django/forms/mutation.py
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
# from django import forms
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
import graphene
|
||||||
|
from graphene import Field, InputField
|
||||||
|
from graphene.relay.mutation import ClientIDMutation
|
||||||
|
from graphene.types.mutation import MutationOptions
|
||||||
|
# from graphene.types.inputobjecttype import (
|
||||||
|
# InputObjectTypeOptions,
|
||||||
|
# InputObjectType,
|
||||||
|
# )
|
||||||
|
from graphene.types.utils import yank_fields_from_attrs
|
||||||
|
from graphene_django.registry import get_global_registry
|
||||||
|
|
||||||
|
from .converter import convert_form_field
|
||||||
|
from .types import ErrorType
|
||||||
|
|
||||||
|
|
||||||
|
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 is_not_in_only or is_excluded:
|
||||||
|
continue
|
||||||
|
|
||||||
|
fields[name] = convert_form_field(field)
|
||||||
|
return fields
|
||||||
|
|
||||||
|
|
||||||
|
class BaseDjangoFormMutation(ClientIDMutation):
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mutate_and_get_payload(cls, root, info, **input):
|
||||||
|
form = cls.get_form(root, info, **input)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
return cls.perform_mutate(form, info)
|
||||||
|
else:
|
||||||
|
errors = [
|
||||||
|
ErrorType(field=key, messages=value)
|
||||||
|
for key, value in form.errors.items()
|
||||||
|
]
|
||||||
|
|
||||||
|
return cls(errors=errors)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_form(cls, root, info, **input):
|
||||||
|
form_kwargs = cls.get_form_kwargs(root, info, **input)
|
||||||
|
return cls._meta.form_class(**form_kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_form_kwargs(cls, root, info, **input):
|
||||||
|
kwargs = {'data': input}
|
||||||
|
|
||||||
|
pk = input.pop('id', None)
|
||||||
|
if pk:
|
||||||
|
instance = cls._meta.model._default_manager.get(pk=pk)
|
||||||
|
kwargs['instance'] = instance
|
||||||
|
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
|
# class DjangoFormInputObjectTypeOptions(InputObjectTypeOptions):
|
||||||
|
# form_class = None
|
||||||
|
|
||||||
|
|
||||||
|
# class DjangoFormInputObjectType(InputObjectType):
|
||||||
|
# class Meta:
|
||||||
|
# abstract = True
|
||||||
|
|
||||||
|
# @classmethod
|
||||||
|
# def __init_subclass_with_meta__(cls, form_class=None,
|
||||||
|
# only_fields=(), exclude_fields=(), _meta=None, **options):
|
||||||
|
# if not _meta:
|
||||||
|
# _meta = DjangoFormInputObjectTypeOptions(cls)
|
||||||
|
# assert isinstance(form_class, forms.Form), (
|
||||||
|
# 'form_class must be an instance of django.forms.Form'
|
||||||
|
# )
|
||||||
|
# _meta.form_class = form_class
|
||||||
|
# form = form_class()
|
||||||
|
# fields = fields_for_form(form, only_fields, exclude_fields)
|
||||||
|
# super(DjangoFormInputObjectType, cls).__init_subclass_with_meta__(_meta=_meta, fields=fields, **options)
|
||||||
|
|
||||||
|
|
||||||
|
class DjangoFormMutationOptions(MutationOptions):
|
||||||
|
form_class = None
|
||||||
|
|
||||||
|
|
||||||
|
class DjangoFormMutation(BaseDjangoFormMutation):
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
errors = graphene.List(ErrorType)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __init_subclass_with_meta__(cls, form_class=None,
|
||||||
|
only_fields=(), exclude_fields=(), **options):
|
||||||
|
|
||||||
|
if not form_class:
|
||||||
|
raise Exception('form_class is required for DjangoFormMutation')
|
||||||
|
|
||||||
|
form = form_class()
|
||||||
|
input_fields = fields_for_form(form, only_fields, exclude_fields)
|
||||||
|
output_fields = fields_for_form(form, only_fields, exclude_fields)
|
||||||
|
|
||||||
|
_meta = DjangoFormMutationOptions(cls)
|
||||||
|
_meta.form_class = form_class
|
||||||
|
_meta.fields = yank_fields_from_attrs(
|
||||||
|
output_fields,
|
||||||
|
_as=Field,
|
||||||
|
)
|
||||||
|
|
||||||
|
input_fields = yank_fields_from_attrs(
|
||||||
|
input_fields,
|
||||||
|
_as=InputField,
|
||||||
|
)
|
||||||
|
super(DjangoFormMutation, cls).__init_subclass_with_meta__(_meta=_meta, input_fields=input_fields, **options)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def perform_mutate(cls, form, info):
|
||||||
|
form.save()
|
||||||
|
return cls(errors=[])
|
||||||
|
|
||||||
|
|
||||||
|
class DjangoModelDjangoFormMutationOptions(DjangoFormMutationOptions):
|
||||||
|
model = None
|
||||||
|
return_field_name = None
|
||||||
|
|
||||||
|
|
||||||
|
class DjangoModelFormMutation(BaseDjangoFormMutation):
|
||||||
|
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 DjangoModelFormMutation')
|
||||||
|
|
||||||
|
if not model:
|
||||||
|
model = form_class._meta.model
|
||||||
|
|
||||||
|
if not model:
|
||||||
|
raise Exception('model is required for DjangoModelFormMutation')
|
||||||
|
|
||||||
|
form = form_class()
|
||||||
|
input_fields = fields_for_form(form, only_fields, exclude_fields)
|
||||||
|
input_fields['id'] = graphene.ID()
|
||||||
|
|
||||||
|
registry = get_global_registry()
|
||||||
|
model_type = registry.get_type_for_model(model)
|
||||||
|
return_field_name = return_field_name
|
||||||
|
if not return_field_name:
|
||||||
|
model_name = model.__name__
|
||||||
|
return_field_name = model_name[:1].lower() + model_name[1:]
|
||||||
|
|
||||||
|
output_fields = OrderedDict()
|
||||||
|
output_fields[return_field_name] = graphene.Field(model_type)
|
||||||
|
|
||||||
|
_meta = DjangoModelDjangoFormMutationOptions(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,
|
||||||
|
)
|
||||||
|
|
||||||
|
input_fields = yank_fields_from_attrs(
|
||||||
|
input_fields,
|
||||||
|
_as=InputField,
|
||||||
|
)
|
||||||
|
super(DjangoModelFormMutation, cls).__init_subclass_with_meta__(
|
||||||
|
_meta=_meta,
|
||||||
|
input_fields=input_fields,
|
||||||
|
**options
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def perform_mutate(cls, form, info):
|
||||||
|
obj = form.save()
|
||||||
|
kwargs = {cls._meta.return_field_name: obj}
|
||||||
|
return cls(errors=[], **kwargs)
|
0
graphene_django/forms/tests/__init__.py
Normal file
0
graphene_django/forms/tests/__init__.py
Normal file
|
@ -2,10 +2,9 @@ from django import forms
|
||||||
from py.test import raises
|
from py.test import raises
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
from graphene import ID, List, NonNull
|
from graphene import String, Int, Boolean, Float, ID, UUID, List, NonNull, DateTime, Date, Time
|
||||||
|
|
||||||
from ..form_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,80 +23,80 @@ def test_should_unknown_django_field_raise_exception():
|
||||||
|
|
||||||
|
|
||||||
def test_should_date_convert_date():
|
def test_should_date_convert_date():
|
||||||
assert_conversion(forms.DateField, graphene.types.datetime.Date)
|
assert_conversion(forms.DateField, Date)
|
||||||
|
|
||||||
|
|
||||||
def test_should_time_convert_time():
|
def test_should_time_convert_time():
|
||||||
assert_conversion(forms.TimeField, graphene.types.datetime.Time)
|
assert_conversion(forms.TimeField, Time)
|
||||||
|
|
||||||
|
|
||||||
def test_should_date_time_convert_date_time():
|
def test_should_date_time_convert_date_time():
|
||||||
assert_conversion(forms.DateTimeField, graphene.types.datetime.DateTime)
|
assert_conversion(forms.DateTimeField, DateTime)
|
||||||
|
|
||||||
|
|
||||||
def test_should_char_convert_string():
|
def test_should_char_convert_string():
|
||||||
assert_conversion(forms.CharField, graphene.String)
|
assert_conversion(forms.CharField, String)
|
||||||
|
|
||||||
|
|
||||||
def test_should_email_convert_string():
|
def test_should_email_convert_string():
|
||||||
assert_conversion(forms.EmailField, graphene.String)
|
assert_conversion(forms.EmailField, String)
|
||||||
|
|
||||||
|
|
||||||
def test_should_slug_convert_string():
|
def test_should_slug_convert_string():
|
||||||
assert_conversion(forms.SlugField, graphene.String)
|
assert_conversion(forms.SlugField, String)
|
||||||
|
|
||||||
|
|
||||||
def test_should_url_convert_string():
|
def test_should_url_convert_string():
|
||||||
assert_conversion(forms.URLField, graphene.String)
|
assert_conversion(forms.URLField, String)
|
||||||
|
|
||||||
|
|
||||||
def test_should_choice_convert_string():
|
def test_should_choice_convert_string():
|
||||||
assert_conversion(forms.ChoiceField, graphene.String)
|
assert_conversion(forms.ChoiceField, String)
|
||||||
|
|
||||||
|
|
||||||
def test_should_base_field_convert_string():
|
def test_should_base_field_convert_string():
|
||||||
assert_conversion(forms.Field, graphene.String)
|
assert_conversion(forms.Field, String)
|
||||||
|
|
||||||
|
|
||||||
def test_should_regex_convert_string():
|
def test_should_regex_convert_string():
|
||||||
assert_conversion(forms.RegexField, graphene.String, '[0-9]+')
|
assert_conversion(forms.RegexField, String, '[0-9]+')
|
||||||
|
|
||||||
|
|
||||||
def test_should_uuid_convert_string():
|
def test_should_uuid_convert_string():
|
||||||
if hasattr(forms, 'UUIDField'):
|
if hasattr(forms, 'UUIDField'):
|
||||||
assert_conversion(forms.UUIDField, graphene.UUID)
|
assert_conversion(forms.UUIDField, UUID)
|
||||||
|
|
||||||
|
|
||||||
def test_should_integer_convert_int():
|
def test_should_integer_convert_int():
|
||||||
assert_conversion(forms.IntegerField, graphene.Int)
|
assert_conversion(forms.IntegerField, Int)
|
||||||
|
|
||||||
|
|
||||||
def test_should_boolean_convert_boolean():
|
def test_should_boolean_convert_boolean():
|
||||||
field = assert_conversion(forms.BooleanField, graphene.Boolean)
|
field = assert_conversion(forms.BooleanField, Boolean)
|
||||||
assert isinstance(field.type, NonNull)
|
assert isinstance(field.type, NonNull)
|
||||||
|
|
||||||
|
|
||||||
def test_should_nullboolean_convert_boolean():
|
def test_should_nullboolean_convert_boolean():
|
||||||
field = assert_conversion(forms.NullBooleanField, graphene.Boolean)
|
field = assert_conversion(forms.NullBooleanField, Boolean)
|
||||||
assert not isinstance(field.type, NonNull)
|
assert not isinstance(field.type, NonNull)
|
||||||
|
|
||||||
|
|
||||||
def test_should_float_convert_float():
|
def test_should_float_convert_float():
|
||||||
assert_conversion(forms.FloatField, graphene.Float)
|
assert_conversion(forms.FloatField, Float)
|
||||||
|
|
||||||
|
|
||||||
def test_should_decimal_convert_float():
|
def test_should_decimal_convert_float():
|
||||||
assert_conversion(forms.DecimalField, graphene.Float)
|
assert_conversion(forms.DecimalField, 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, ID)
|
113
graphene_django/forms/tests/test_mutation.py
Normal file
113
graphene_django/forms/tests/test_mutation.py
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
from django import forms
|
||||||
|
from django.test import TestCase
|
||||||
|
from py.test import raises
|
||||||
|
|
||||||
|
from graphene_django.tests.models import Pet, Film, FilmDetails
|
||||||
|
from ..mutation import DjangoFormMutation, DjangoModelFormMutation
|
||||||
|
|
||||||
|
|
||||||
|
class MyForm(forms.Form):
|
||||||
|
text = forms.CharField()
|
||||||
|
|
||||||
|
|
||||||
|
class PetForm(forms.ModelForm):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Pet
|
||||||
|
fields = ('name',)
|
||||||
|
|
||||||
|
|
||||||
|
def test_needs_form_class():
|
||||||
|
with raises(Exception) as exc:
|
||||||
|
class MyMutation(DjangoFormMutation):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert exc.value.args[0] == 'form_class is required for DjangoFormMutation'
|
||||||
|
|
||||||
|
|
||||||
|
def test_has_output_fields():
|
||||||
|
class MyMutation(DjangoFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = MyForm
|
||||||
|
|
||||||
|
assert 'errors' in MyMutation._meta.fields
|
||||||
|
|
||||||
|
|
||||||
|
def test_has_input_fields():
|
||||||
|
class MyMutation(DjangoFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = MyForm
|
||||||
|
|
||||||
|
assert 'text' in MyMutation.Input._meta.fields
|
||||||
|
|
||||||
|
|
||||||
|
class ModelFormMutationTests(TestCase):
|
||||||
|
|
||||||
|
def test_default_meta_fields(self):
|
||||||
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = PetForm
|
||||||
|
|
||||||
|
self.assertEqual(PetMutation._meta.model, Pet)
|
||||||
|
self.assertEqual(PetMutation._meta.return_field_name, 'pet')
|
||||||
|
self.assertIn('pet', PetMutation._meta.fields)
|
||||||
|
|
||||||
|
def test_return_field_name_is_camelcased(self):
|
||||||
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = PetForm
|
||||||
|
model = FilmDetails
|
||||||
|
|
||||||
|
self.assertEqual(PetMutation._meta.model, FilmDetails)
|
||||||
|
self.assertEqual(PetMutation._meta.return_field_name, 'filmDetails')
|
||||||
|
|
||||||
|
def test_custom_return_field_name(self):
|
||||||
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
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(DjangoModelFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = PetForm
|
||||||
|
|
||||||
|
pet = Pet.objects.create(name='Axel')
|
||||||
|
|
||||||
|
result = PetMutation.mutate_and_get_payload(None, None, id=pet.pk, name='Mia')
|
||||||
|
|
||||||
|
self.assertEqual(Pet.objects.count(), 1)
|
||||||
|
pet.refresh_from_db()
|
||||||
|
self.assertEqual(pet.name, 'Mia')
|
||||||
|
self.assertEqual(result.errors, [])
|
||||||
|
|
||||||
|
def test_model_form_mutation_updates_existing_(self):
|
||||||
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = PetForm
|
||||||
|
|
||||||
|
result = PetMutation.mutate_and_get_payload(None, None, name='Mia')
|
||||||
|
|
||||||
|
self.assertEqual(Pet.objects.count(), 1)
|
||||||
|
pet = Pet.objects.get()
|
||||||
|
self.assertEqual(pet.name, 'Mia')
|
||||||
|
self.assertEqual(result.errors, [])
|
||||||
|
|
||||||
|
def test_model_form_mutation_mutate_invalid_form(self):
|
||||||
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = PetForm
|
||||||
|
|
||||||
|
result = PetMutation.mutate_and_get_payload(None, None)
|
||||||
|
|
||||||
|
# A pet was not created
|
||||||
|
self.assertEqual(Pet.objects.count(), 0)
|
||||||
|
|
||||||
|
self.assertEqual(len(result.errors), 1)
|
||||||
|
self.assertEqual(result.errors[0].field, 'name')
|
||||||
|
self.assertEqual(result.errors[0].messages, ['This field is required.'])
|
6
graphene_django/forms/types.py
Normal file
6
graphene_django/forms/types.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import graphene
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorType(graphene.ObjectType):
|
||||||
|
field = graphene.String()
|
||||||
|
messages = graphene.List(graphene.String)
|
Loading…
Reference in New Issue
Block a user