mirror of
https://github.com/graphql-python/graphene-django.git
synced 2024-12-01 22:13:47 +03:00
Apply camel case converter to field names in DRF errors (#514)
* Apply camel case converter to field names in DRF errors * Implement recursive error camelize, add setting.
This commit is contained in:
parent
692540cc78
commit
e2e496f505
|
@ -13,8 +13,8 @@ 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 .converter import convert_form_field
|
|
||||||
from ..types import ErrorType
|
from ..types import ErrorType
|
||||||
|
from .converter import convert_form_field
|
||||||
|
|
||||||
|
|
||||||
def fields_for_form(form, only_fields, exclude_fields):
|
def fields_for_form(form, only_fields, exclude_fields):
|
||||||
|
@ -45,10 +45,7 @@ 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 = [
|
errors = ErrorType.from_errors(form.errors)
|
||||||
ErrorType(field=key, messages=value)
|
|
||||||
for key, value in form.errors.items()
|
|
||||||
]
|
|
||||||
|
|
||||||
return cls(errors=errors)
|
return cls(errors=errors)
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,9 @@ 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, Film, FilmDetails
|
from graphene_django.tests.models import Film, FilmDetails, Pet
|
||||||
|
|
||||||
|
from ...settings import graphene_settings
|
||||||
from ..mutation import DjangoFormMutation, DjangoModelFormMutation
|
from ..mutation import DjangoFormMutation, DjangoModelFormMutation
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,6 +43,22 @@ def test_has_input_fields():
|
||||||
assert "text" in MyMutation.Input._meta.fields
|
assert "text" in MyMutation.Input._meta.fields
|
||||||
|
|
||||||
|
|
||||||
|
def test_mutation_error_camelcased():
|
||||||
|
class ExtraPetForm(PetForm):
|
||||||
|
test_field = forms.CharField(required=True)
|
||||||
|
|
||||||
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
class Meta:
|
||||||
|
form_class = ExtraPetForm
|
||||||
|
|
||||||
|
result = PetMutation.mutate_and_get_payload(None, None)
|
||||||
|
assert {f.field for f in result.errors} == {"name", "age", "test_field"}
|
||||||
|
graphene_settings.DJANGO_GRAPHENE_CAMELCASE_ERRORS = True
|
||||||
|
result = PetMutation.mutate_and_get_payload(None, None)
|
||||||
|
assert {f.field for f in result.errors} == {"name", "age", "testField"}
|
||||||
|
graphene_settings.DJANGO_GRAPHENE_CAMELCASE_ERRORS = False
|
||||||
|
|
||||||
|
|
||||||
class ModelFormMutationTests(TestCase):
|
class ModelFormMutationTests(TestCase):
|
||||||
def test_default_meta_fields(self):
|
def test_default_meta_fields(self):
|
||||||
class PetMutation(DjangoModelFormMutation):
|
class PetMutation(DjangoModelFormMutation):
|
||||||
|
|
|
@ -3,13 +3,13 @@ from collections import OrderedDict
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
|
from graphene.relay.mutation import ClientIDMutation
|
||||||
from graphene.types import Field, InputField
|
from graphene.types import Field, InputField
|
||||||
from graphene.types.mutation import MutationOptions
|
from graphene.types.mutation import MutationOptions
|
||||||
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 .serializer_converter import convert_serializer_field
|
|
||||||
from ..types import ErrorType
|
from ..types import ErrorType
|
||||||
|
from .serializer_converter import convert_serializer_field
|
||||||
|
|
||||||
|
|
||||||
class SerializerMutationOptions(MutationOptions):
|
class SerializerMutationOptions(MutationOptions):
|
||||||
|
@ -127,10 +127,7 @@ class SerializerMutation(ClientIDMutation):
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
return cls.perform_mutate(serializer, info)
|
return cls.perform_mutate(serializer, info)
|
||||||
else:
|
else:
|
||||||
errors = [
|
errors = ErrorType.from_errors(serializer.errors)
|
||||||
ErrorType(field=key, messages=value)
|
|
||||||
for key, value in serializer.errors.items()
|
|
||||||
]
|
|
||||||
|
|
||||||
return cls(errors=errors)
|
return cls(errors=errors)
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
from py.test import mark, raises
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
from graphene import Field, ResolveInfo
|
from graphene import Field, ResolveInfo
|
||||||
from graphene.types.inputobjecttype import InputObjectType
|
from graphene.types.inputobjecttype import InputObjectType
|
||||||
from py.test import raises
|
|
||||||
from py.test import mark
|
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
|
from ...settings import graphene_settings
|
||||||
from ...types import DjangoObjectType
|
from ...types import DjangoObjectType
|
||||||
from ..models import MyFakeModel, MyFakeModelWithPassword
|
from ..models import MyFakeModel, MyFakeModelWithPassword
|
||||||
from ..mutation import SerializerMutation
|
from ..mutation import SerializerMutation
|
||||||
|
@ -213,6 +214,13 @@ def test_model_mutate_and_get_payload_error():
|
||||||
assert len(result.errors) > 0
|
assert len(result.errors) > 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_mutation_error_camelcased():
|
||||||
|
graphene_settings.DJANGO_GRAPHENE_CAMELCASE_ERRORS = True
|
||||||
|
result = MyModelMutation.mutate_and_get_payload(None, mock_info(), **{})
|
||||||
|
assert result.errors[0].field == "coolName"
|
||||||
|
graphene_settings.DJANGO_GRAPHENE_CAMELCASE_ERRORS = False
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_serializer_operations():
|
def test_invalid_serializer_operations():
|
||||||
with raises(Exception) as exc:
|
with raises(Exception) as exc:
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ DEFAULTS = {
|
||||||
"RELAY_CONNECTION_ENFORCE_FIRST_OR_LAST": False,
|
"RELAY_CONNECTION_ENFORCE_FIRST_OR_LAST": False,
|
||||||
# Max items returned in ConnectionFields / FilterConnectionFields
|
# Max items returned in ConnectionFields / FilterConnectionFields
|
||||||
"RELAY_CONNECTION_MAX_LIMIT": 100,
|
"RELAY_CONNECTION_MAX_LIMIT": 100,
|
||||||
|
"DJANGO_GRAPHENE_CAMELCASE_ERRORS": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
from ..utils import get_model_fields
|
from django.utils.translation import gettext_lazy
|
||||||
|
|
||||||
|
from ..utils import camelize, get_model_fields
|
||||||
from .models import Film, Reporter
|
from .models import Film, Reporter
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,3 +12,21 @@ def test_get_model_fields_no_duplication():
|
||||||
film_fields = get_model_fields(Film)
|
film_fields = get_model_fields(Film)
|
||||||
film_name_set = set([field[0] for field in film_fields])
|
film_name_set = set([field[0] for field in film_fields])
|
||||||
assert len(film_fields) == len(film_name_set)
|
assert len(film_fields) == len(film_name_set)
|
||||||
|
|
||||||
|
|
||||||
|
def test_camelize():
|
||||||
|
assert camelize({}) == {}
|
||||||
|
assert camelize("value_a") == "value_a"
|
||||||
|
assert camelize({"value_a": "value_b"}) == {"valueA": "value_b"}
|
||||||
|
assert camelize({"value_a": ["value_b"]}) == {"valueA": ["value_b"]}
|
||||||
|
assert camelize({"value_a": ["value_b"]}) == {"valueA": ["value_b"]}
|
||||||
|
assert camelize({"nested_field": {"value_a": ["error"], "value_b": ["error"]}}) == {
|
||||||
|
"nestedField": {"valueA": ["error"], "valueB": ["error"]}
|
||||||
|
}
|
||||||
|
assert camelize({"value_a": gettext_lazy("value_b")}) == {"valueA": "value_b"}
|
||||||
|
assert camelize({"value_a": [gettext_lazy("value_b")]}) == {"valueA": ["value_b"]}
|
||||||
|
assert camelize(gettext_lazy("value_a")) == "value_a"
|
||||||
|
assert camelize({gettext_lazy("value_a"): gettext_lazy("value_b")}) == {
|
||||||
|
"valueA": "value_b"
|
||||||
|
}
|
||||||
|
assert camelize({0: {"field_a": ["errors"]}}) == {0: {"fieldA": ["errors"]}}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import six
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
import six
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
from django.utils.functional import SimpleLazyObject
|
from django.utils.functional import SimpleLazyObject
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
from graphene import Field
|
from graphene import Field
|
||||||
from graphene.relay import Connection, Node
|
from graphene.relay import Connection, Node
|
||||||
|
@ -11,8 +12,13 @@ from graphene.types.utils import yank_fields_from_attrs
|
||||||
|
|
||||||
from .converter import convert_django_field_with_choices
|
from .converter import convert_django_field_with_choices
|
||||||
from .registry import Registry, get_global_registry
|
from .registry import Registry, get_global_registry
|
||||||
from .utils import DJANGO_FILTER_INSTALLED, get_model_fields, is_valid_django_model
|
from .settings import graphene_settings
|
||||||
|
from .utils import (
|
||||||
|
DJANGO_FILTER_INSTALLED,
|
||||||
|
camelize,
|
||||||
|
get_model_fields,
|
||||||
|
is_valid_django_model,
|
||||||
|
)
|
||||||
|
|
||||||
if six.PY3:
|
if six.PY3:
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
@ -182,3 +188,12 @@ class DjangoObjectType(ObjectType):
|
||||||
class ErrorType(ObjectType):
|
class ErrorType(ObjectType):
|
||||||
field = graphene.String(required=True)
|
field = graphene.String(required=True)
|
||||||
messages = graphene.List(graphene.NonNull(graphene.String), required=True)
|
messages = graphene.List(graphene.NonNull(graphene.String), required=True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_errors(cls, errors):
|
||||||
|
data = (
|
||||||
|
camelize(errors)
|
||||||
|
if graphene_settings.DJANGO_GRAPHENE_CAMELCASE_ERRORS
|
||||||
|
else errors
|
||||||
|
)
|
||||||
|
return [ErrorType(field=key, messages=value) for key, value in data.items()]
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
|
from .testing import GraphQLTestCase
|
||||||
from .utils import (
|
from .utils import (
|
||||||
DJANGO_FILTER_INSTALLED,
|
DJANGO_FILTER_INSTALLED,
|
||||||
get_reverse_fields,
|
camelize,
|
||||||
maybe_queryset,
|
|
||||||
get_model_fields,
|
get_model_fields,
|
||||||
is_valid_django_model,
|
get_reverse_fields,
|
||||||
import_single_dispatch,
|
import_single_dispatch,
|
||||||
|
is_valid_django_model,
|
||||||
|
maybe_queryset,
|
||||||
)
|
)
|
||||||
from .testing import GraphQLTestCase
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"DJANGO_FILTER_INSTALLED",
|
"DJANGO_FILTER_INSTALLED",
|
||||||
"get_reverse_fields",
|
"get_reverse_fields",
|
||||||
"maybe_queryset",
|
"maybe_queryset",
|
||||||
"get_model_fields",
|
"get_model_fields",
|
||||||
|
"camelize",
|
||||||
"is_valid_django_model",
|
"is_valid_django_model",
|
||||||
"import_single_dispatch",
|
"import_single_dispatch",
|
||||||
"GraphQLTestCase",
|
"GraphQLTestCase",
|
||||||
|
|
|
@ -2,7 +2,11 @@ import inspect
|
||||||
|
|
||||||
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 django.utils import six
|
||||||
|
from django.utils.encoding import force_text
|
||||||
|
from django.utils.functional import Promise
|
||||||
|
|
||||||
|
from graphene.utils.str_converters import to_camel_case
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import django_filters # noqa
|
import django_filters # noqa
|
||||||
|
@ -12,6 +16,28 @@ except ImportError:
|
||||||
DJANGO_FILTER_INSTALLED = False
|
DJANGO_FILTER_INSTALLED = False
|
||||||
|
|
||||||
|
|
||||||
|
def isiterable(value):
|
||||||
|
try:
|
||||||
|
iter(value)
|
||||||
|
except TypeError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _camelize_django_str(s):
|
||||||
|
if isinstance(s, Promise):
|
||||||
|
s = force_text(s)
|
||||||
|
return to_camel_case(s) if isinstance(s, six.string_types) else s
|
||||||
|
|
||||||
|
|
||||||
|
def camelize(data):
|
||||||
|
if isinstance(data, dict):
|
||||||
|
return {_camelize_django_str(k): camelize(v) for k, v in data.items()}
|
||||||
|
if isiterable(data) and not isinstance(data, (six.string_types, Promise)):
|
||||||
|
return [camelize(d) for d in data]
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
def get_reverse_fields(model, local_field_names):
|
def get_reverse_fields(model, local_field_names):
|
||||||
for name, attr in model.__dict__.items():
|
for name, attr in model.__dict__.items():
|
||||||
# Don't duplicate any local fields
|
# Don't duplicate any local fields
|
||||||
|
|
Loading…
Reference in New Issue
Block a user