mirror of
https://github.com/graphql-python/graphene-django.git
synced 2025-07-14 02:02:20 +03:00
Update form mutation errors
This commit is contained in:
parent
dafcc8514b
commit
19fcc3cdf2
|
@ -13,6 +13,7 @@ from graphene.types.mutation import MutationOptions
|
||||||
from graphene.types.utils import yank_fields_from_attrs
|
from graphene.types.utils import yank_fields_from_attrs
|
||||||
from graphene_django.registry import get_global_registry
|
from graphene_django.registry import get_global_registry
|
||||||
|
|
||||||
|
from ..utils import create_errors_type
|
||||||
from .converter import convert_form_field
|
from .converter import convert_form_field
|
||||||
from .types import ErrorType
|
from .types import ErrorType
|
||||||
|
|
||||||
|
@ -45,10 +46,8 @@ class BaseDjangoFormMutation(ClientIDMutation):
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
return cls.perform_mutate(form, info)
|
return cls.perform_mutate(form, info)
|
||||||
else:
|
else:
|
||||||
errors = [
|
# TODO: double check non field errors name
|
||||||
ErrorType(field=key, messages=value)
|
errors = cls.Errors(**form.errors)
|
||||||
for key, value in form.errors.items()
|
|
||||||
]
|
|
||||||
|
|
||||||
return cls(errors=errors)
|
return cls(errors=errors)
|
||||||
|
|
||||||
|
@ -99,8 +98,6 @@ class DjangoFormMutation(BaseDjangoFormMutation):
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
errors = graphene.List(ErrorType)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __init_subclass_with_meta__(
|
def __init_subclass_with_meta__(
|
||||||
cls, form_class=None, only_fields=(), exclude_fields=(), **options
|
cls, form_class=None, only_fields=(), exclude_fields=(), **options
|
||||||
|
@ -112,12 +109,21 @@ class DjangoFormMutation(BaseDjangoFormMutation):
|
||||||
form = form_class()
|
form = form_class()
|
||||||
input_fields = fields_for_form(form, only_fields, exclude_fields)
|
input_fields = fields_for_form(form, only_fields, exclude_fields)
|
||||||
output_fields = fields_for_form(form, only_fields, exclude_fields)
|
output_fields = fields_for_form(form, only_fields, exclude_fields)
|
||||||
|
input_fields = yank_fields_from_attrs(input_fields, _as=InputField)
|
||||||
|
|
||||||
|
base_name = cls.__name__
|
||||||
|
|
||||||
|
cls.Errors = create_errors_type(
|
||||||
|
"{}Errors".format(base_name),
|
||||||
|
input_fields
|
||||||
|
)
|
||||||
|
|
||||||
|
output_fields['errors'] = graphene.Field(cls.Errors, required=True)
|
||||||
|
|
||||||
_meta = DjangoFormMutationOptions(cls)
|
_meta = DjangoFormMutationOptions(cls)
|
||||||
_meta.form_class = form_class
|
_meta.form_class = form_class
|
||||||
_meta.fields = yank_fields_from_attrs(output_fields, _as=Field)
|
_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__(
|
super(DjangoFormMutation, cls).__init_subclass_with_meta__(
|
||||||
_meta=_meta, input_fields=input_fields, **options
|
_meta=_meta, input_fields=input_fields, **options
|
||||||
)
|
)
|
||||||
|
@ -137,8 +143,6 @@ class DjangoModelFormMutation(BaseDjangoFormMutation):
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
errors = graphene.List(ErrorType)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __init_subclass_with_meta__(
|
def __init_subclass_with_meta__(
|
||||||
cls,
|
cls,
|
||||||
|
@ -173,6 +177,16 @@ class DjangoModelFormMutation(BaseDjangoFormMutation):
|
||||||
|
|
||||||
output_fields = OrderedDict()
|
output_fields = OrderedDict()
|
||||||
output_fields[return_field_name] = graphene.Field(model_type)
|
output_fields[return_field_name] = graphene.Field(model_type)
|
||||||
|
input_fields = yank_fields_from_attrs(input_fields, _as=InputField)
|
||||||
|
|
||||||
|
base_name = cls.__name__
|
||||||
|
|
||||||
|
cls.Errors = create_errors_type(
|
||||||
|
"{}Errors".format(base_name),
|
||||||
|
input_fields
|
||||||
|
)
|
||||||
|
|
||||||
|
output_fields['errors'] = graphene.Field(cls.Errors, required=True)
|
||||||
|
|
||||||
_meta = DjangoModelDjangoFormMutationOptions(cls)
|
_meta = DjangoModelDjangoFormMutationOptions(cls)
|
||||||
_meta.form_class = form_class
|
_meta.form_class = form_class
|
||||||
|
@ -180,7 +194,6 @@ class DjangoModelFormMutation(BaseDjangoFormMutation):
|
||||||
_meta.return_field_name = return_field_name
|
_meta.return_field_name = return_field_name
|
||||||
_meta.fields = yank_fields_from_attrs(output_fields, _as=Field)
|
_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__(
|
super(DjangoModelFormMutation, cls).__init_subclass_with_meta__(
|
||||||
_meta=_meta, input_fields=input_fields, **options
|
_meta=_meta, input_fields=input_fields, **options
|
||||||
)
|
)
|
||||||
|
@ -189,4 +202,4 @@ class DjangoModelFormMutation(BaseDjangoFormMutation):
|
||||||
def perform_mutate(cls, form, info):
|
def perform_mutate(cls, form, info):
|
||||||
obj = form.save()
|
obj = form.save()
|
||||||
kwargs = {cls._meta.return_field_name: obj}
|
kwargs = {cls._meta.return_field_name: obj}
|
||||||
return cls(errors=[], **kwargs)
|
return cls(errors={}, **kwargs)
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from py.test import raises
|
|
||||||
|
from graphene import NonNull, List
|
||||||
|
|
||||||
from graphene_django.tests.models import Pet, Film, FilmDetails
|
from graphene_django.tests.models import Pet, Film, FilmDetails
|
||||||
from ..mutation import DjangoFormMutation, DjangoModelFormMutation
|
from ..mutation import DjangoFormMutation, DjangoModelFormMutation
|
||||||
|
@ -17,8 +20,7 @@ class PetForm(forms.ModelForm):
|
||||||
|
|
||||||
|
|
||||||
def test_needs_form_class():
|
def test_needs_form_class():
|
||||||
with raises(Exception) as exc:
|
with pytest.raises(Exception) as exc:
|
||||||
|
|
||||||
class MyMutation(DjangoFormMutation):
|
class MyMutation(DjangoFormMutation):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -41,101 +43,127 @@ def test_has_input_fields():
|
||||||
assert "text" in MyMutation.Input._meta.fields
|
assert "text" in MyMutation.Input._meta.fields
|
||||||
|
|
||||||
|
|
||||||
class ModelFormMutationTests(TestCase):
|
def test_default_meta_fields():
|
||||||
def test_default_meta_fields(self):
|
class PetMutation(DjangoModelFormMutation):
|
||||||
class PetMutation(DjangoModelFormMutation):
|
class Meta:
|
||||||
class Meta:
|
form_class = PetForm
|
||||||
form_class = PetForm
|
|
||||||
|
|
||||||
self.assertEqual(PetMutation._meta.model, Pet)
|
assert PetMutation._meta.model == Pet
|
||||||
self.assertEqual(PetMutation._meta.return_field_name, "pet")
|
assert PetMutation._meta.return_field_name == "pet"
|
||||||
self.assertIn("pet", PetMutation._meta.fields)
|
|
||||||
|
|
||||||
def test_default_input_meta_fields(self):
|
assert "pet" in PetMutation._meta.fields
|
||||||
class PetMutation(DjangoModelFormMutation):
|
|
||||||
class Meta:
|
|
||||||
form_class = PetForm
|
|
||||||
|
|
||||||
self.assertEqual(PetMutation._meta.model, Pet)
|
|
||||||
self.assertEqual(PetMutation._meta.return_field_name, "pet")
|
|
||||||
self.assertIn("name", PetMutation.Input._meta.fields)
|
|
||||||
self.assertIn("client_mutation_id", PetMutation.Input._meta.fields)
|
|
||||||
self.assertIn("id", PetMutation.Input._meta.fields)
|
|
||||||
|
|
||||||
def test_exclude_fields_input_meta_fields(self):
|
|
||||||
class PetMutation(DjangoModelFormMutation):
|
|
||||||
class Meta:
|
|
||||||
form_class = PetForm
|
|
||||||
exclude_fields = ['id']
|
|
||||||
|
|
||||||
self.assertEqual(PetMutation._meta.model, Pet)
|
|
||||||
self.assertEqual(PetMutation._meta.return_field_name, "pet")
|
|
||||||
self.assertIn("name", PetMutation.Input._meta.fields)
|
|
||||||
self.assertIn("age", PetMutation.Input._meta.fields)
|
|
||||||
self.assertIn("client_mutation_id", PetMutation.Input._meta.fields)
|
|
||||||
self.assertNotIn("id", PetMutation.Input._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", age=10)
|
|
||||||
|
|
||||||
result = PetMutation.mutate_and_get_payload(None, None, id=pet.pk, name="Mia", age=10)
|
|
||||||
|
|
||||||
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", age=10)
|
|
||||||
|
|
||||||
self.assertEqual(Pet.objects.count(), 1)
|
|
||||||
pet = Pet.objects.get()
|
|
||||||
self.assertEqual(pet.name, "Mia")
|
|
||||||
self.assertEqual(pet.age, 10)
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
fields_w_error = [e.field for e in result.errors]
|
def test_default_input_meta_fields():
|
||||||
self.assertEqual(len(result.errors), 2)
|
class PetMutation(DjangoModelFormMutation):
|
||||||
self.assertIn("name", fields_w_error)
|
class Meta:
|
||||||
self.assertEqual(result.errors[0].messages, ["This field is required."])
|
form_class = PetForm
|
||||||
self.assertIn("age", fields_w_error)
|
|
||||||
self.assertEqual(result.errors[1].messages, ["This field is required."])
|
assert PetMutation._meta.model == Pet
|
||||||
|
assert PetMutation._meta.return_field_name == "pet"
|
||||||
|
|
||||||
|
assert "name" in PetMutation.Input._meta.fields
|
||||||
|
assert "client_mutation_id" in PetMutation.Input._meta.fields
|
||||||
|
assert "id" in PetMutation.Input._meta.fields
|
||||||
|
|
||||||
|
|
||||||
|
def test_exclude_fields_input_meta_fields():
|
||||||
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = PetForm
|
||||||
|
exclude_fields = ['id']
|
||||||
|
|
||||||
|
assert PetMutation._meta.model == Pet
|
||||||
|
assert PetMutation._meta.return_field_name == "pet"
|
||||||
|
assert "name" in PetMutation.Input._meta.fields
|
||||||
|
assert "age" in PetMutation.Input._meta.fields
|
||||||
|
assert "client_mutation_id" in PetMutation.Input._meta.fields
|
||||||
|
assert "id" not in PetMutation.Input._meta.fields
|
||||||
|
|
||||||
|
|
||||||
|
def test_return_field_name_is_camelcased():
|
||||||
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = PetForm
|
||||||
|
model = FilmDetails
|
||||||
|
|
||||||
|
assert PetMutation._meta.model == FilmDetails
|
||||||
|
assert PetMutation._meta.return_field_name == "filmDetails"
|
||||||
|
|
||||||
|
|
||||||
|
def test_custom_return_field_name():
|
||||||
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = PetForm
|
||||||
|
model = Film
|
||||||
|
return_field_name = "animal"
|
||||||
|
|
||||||
|
assert PetMutation._meta.model == Film
|
||||||
|
assert PetMutation._meta.return_field_name == "animal"
|
||||||
|
assert "animal" in PetMutation._meta.fields
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_model_form_mutation_mutate():
|
||||||
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = PetForm
|
||||||
|
|
||||||
|
pet = Pet.objects.create(name="Axel", age=10)
|
||||||
|
|
||||||
|
result = PetMutation.mutate_and_get_payload(None, None, id=pet.pk, name="Mia", age=10)
|
||||||
|
|
||||||
|
assert Pet.objects.count() == 1
|
||||||
|
pet.refresh_from_db()
|
||||||
|
assert pet.name == "Mia"
|
||||||
|
assert result.errors == {}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_model_form_mutation_updates_existing():
|
||||||
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = PetForm
|
||||||
|
|
||||||
|
result = PetMutation.mutate_and_get_payload(None, None, name="Mia", age=10)
|
||||||
|
|
||||||
|
assert Pet.objects.count() == 1
|
||||||
|
pet = Pet.objects.get()
|
||||||
|
assert pet.name == "Mia"
|
||||||
|
assert pet.age == 10
|
||||||
|
assert result.errors == {}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_model_form_mutation_mutate_invalid_form():
|
||||||
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = PetForm
|
||||||
|
|
||||||
|
result = PetMutation.mutate_and_get_payload(None, None)
|
||||||
|
|
||||||
|
# A pet was not created
|
||||||
|
assert Pet.objects.count() == 0
|
||||||
|
|
||||||
|
assert result.errors != {}
|
||||||
|
|
||||||
|
assert result.errors.name == ["This field is required."]
|
||||||
|
assert result.errors.age == ["This field is required."]
|
||||||
|
|
||||||
|
|
||||||
|
def test_errors_field():
|
||||||
|
class MyMutation(DjangoFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = MyForm
|
||||||
|
|
||||||
|
errors_field = MyMutation._meta.fields['errors']
|
||||||
|
|
||||||
|
assert MyMutation.Errors
|
||||||
|
|
||||||
|
assert type(errors_field.type) == NonNull
|
||||||
|
|
||||||
|
errors_field = errors_field.type.of_type
|
||||||
|
|
||||||
|
assert type(errors_field.text.type) == List
|
||||||
|
assert type(errors_field.text.type.of_type) == NonNull
|
||||||
|
# TODO: how to test that the nonnull type is a string?
|
||||||
|
|
|
@ -8,6 +8,7 @@ from graphene.types.mutation import MutationOptions
|
||||||
from graphene.relay.mutation import ClientIDMutation
|
from graphene.relay.mutation import ClientIDMutation
|
||||||
from graphene.types.objecttype import yank_fields_from_attrs
|
from graphene.types.objecttype import yank_fields_from_attrs
|
||||||
|
|
||||||
|
from ..utils import create_errors_type
|
||||||
from .serializer_converter import convert_serializer_field
|
from .serializer_converter import convert_serializer_field
|
||||||
from .types import ErrorType
|
from .types import ErrorType
|
||||||
|
|
||||||
|
@ -73,22 +74,11 @@ class SerializerMutation(ClientIDMutation):
|
||||||
serializer, only_fields, exclude_fields, is_input=False
|
serializer, only_fields, exclude_fields, is_input=False
|
||||||
)
|
)
|
||||||
|
|
||||||
error_fields = {
|
|
||||||
key: graphene.List(graphene.NonNull(graphene.String))
|
|
||||||
for key in input_fields.keys()
|
|
||||||
}
|
|
||||||
|
|
||||||
# TODO: from settings
|
|
||||||
error_fields['non_field_errors'] = graphene.List(
|
|
||||||
graphene.NonNull(graphene.String)
|
|
||||||
)
|
|
||||||
|
|
||||||
base_name = cls.__name__
|
base_name = cls.__name__
|
||||||
|
|
||||||
cls.Errors = type(
|
cls.Errors = create_errors_type(
|
||||||
"{}Errors".format(base_name),
|
"{}Errors".format(base_name),
|
||||||
(graphene.ObjectType, ),
|
input_fields
|
||||||
yank_fields_from_attrs(error_fields, _as=Field),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
output_fields['errors'] = graphene.Field(cls.Errors, required=True)
|
output_fields['errors'] = graphene.Field(cls.Errors, required=True)
|
||||||
|
|
|
@ -159,14 +159,14 @@ def test_mutate_and_get_payload_error():
|
||||||
|
|
||||||
# missing required fields
|
# missing required fields
|
||||||
result = MyMutation.mutate_and_get_payload(None, mock_info(), **{})
|
result = MyMutation.mutate_and_get_payload(None, mock_info(), **{})
|
||||||
assert result.errors.text[0] == "This field is required."
|
assert result.errors.text == ["This field is required."]
|
||||||
assert result.errors.model[0] == "This field is required."
|
assert result.errors.model == ["This field is required."]
|
||||||
|
|
||||||
|
|
||||||
def test_model_mutate_and_get_payload_error():
|
def test_model_mutate_and_get_payload_error():
|
||||||
# missing required fields
|
# missing required fields
|
||||||
result = MyModelMutation.mutate_and_get_payload(None, mock_info(), **{})
|
result = MyModelMutation.mutate_and_get_payload(None, mock_info(), **{})
|
||||||
assert result.errors.cool_name[0] == "This field is required."
|
assert result.errors.cool_name == ["This field is required."]
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_serializer_operations():
|
def test_invalid_serializer_operations():
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
|
import graphene
|
||||||
|
from graphene.types.objecttype import yank_fields_from_attrs
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.manager import Manager
|
from django.db.models.manager import Manager
|
||||||
|
|
||||||
|
|
||||||
# from graphene.utils import LazyList
|
|
||||||
|
|
||||||
|
|
||||||
class LazyList(object):
|
class LazyList(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -81,3 +81,21 @@ def import_single_dispatch():
|
||||||
)
|
)
|
||||||
|
|
||||||
return singledispatch
|
return singledispatch
|
||||||
|
|
||||||
|
|
||||||
|
def create_errors_type(name, input_fields):
|
||||||
|
error_fields = {
|
||||||
|
key: graphene.List(graphene.NonNull(graphene.String))
|
||||||
|
for key in input_fields.keys()
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO: from settings
|
||||||
|
error_fields['non_field_errors'] = graphene.List(
|
||||||
|
graphene.NonNull(graphene.String)
|
||||||
|
)
|
||||||
|
|
||||||
|
return type(
|
||||||
|
name,
|
||||||
|
(graphene.ObjectType, ),
|
||||||
|
yank_fields_from_attrs(error_fields, _as=graphene.Field),
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user