mirror of
https://github.com/graphql-python/graphene-django.git
synced 2024-11-26 03:24:07 +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_django.registry import get_global_registry
|
||||
|
||||
from .converter import convert_form_field
|
||||
from ..types import ErrorType
|
||||
from .converter import convert_form_field
|
||||
|
||||
|
||||
def fields_for_form(form, only_fields, exclude_fields):
|
||||
|
@ -45,10 +45,7 @@ class BaseDjangoFormMutation(ClientIDMutation):
|
|||
if form.is_valid():
|
||||
return cls.perform_mutate(form, info)
|
||||
else:
|
||||
errors = [
|
||||
ErrorType(field=key, messages=value)
|
||||
for key, value in form.errors.items()
|
||||
]
|
||||
errors = ErrorType.from_errors(form.errors)
|
||||
|
||||
return cls(errors=errors)
|
||||
|
||||
|
|
|
@ -2,7 +2,9 @@ from django import forms
|
|||
from django.test import TestCase
|
||||
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
|
||||
|
||||
|
||||
|
@ -41,6 +43,22 @@ def test_has_input_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):
|
||||
def test_default_meta_fields(self):
|
||||
class PetMutation(DjangoModelFormMutation):
|
||||
|
|
|
@ -3,13 +3,13 @@ from collections import OrderedDict
|
|||
from django.shortcuts import get_object_or_404
|
||||
|
||||
import graphene
|
||||
from graphene.relay.mutation import ClientIDMutation
|
||||
from graphene.types import Field, InputField
|
||||
from graphene.types.mutation import MutationOptions
|
||||
from graphene.relay.mutation import ClientIDMutation
|
||||
from graphene.types.objecttype import yank_fields_from_attrs
|
||||
|
||||
from .serializer_converter import convert_serializer_field
|
||||
from ..types import ErrorType
|
||||
from .serializer_converter import convert_serializer_field
|
||||
|
||||
|
||||
class SerializerMutationOptions(MutationOptions):
|
||||
|
@ -127,10 +127,7 @@ class SerializerMutation(ClientIDMutation):
|
|||
if serializer.is_valid():
|
||||
return cls.perform_mutate(serializer, info)
|
||||
else:
|
||||
errors = [
|
||||
ErrorType(field=key, messages=value)
|
||||
for key, value in serializer.errors.items()
|
||||
]
|
||||
errors = ErrorType.from_errors(serializer.errors)
|
||||
|
||||
return cls(errors=errors)
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import datetime
|
||||
|
||||
from py.test import mark, raises
|
||||
from rest_framework import serializers
|
||||
|
||||
from graphene import Field, ResolveInfo
|
||||
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 ..models import MyFakeModel, MyFakeModelWithPassword
|
||||
from ..mutation import SerializerMutation
|
||||
|
@ -213,6 +214,13 @@ def test_model_mutate_and_get_payload_error():
|
|||
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():
|
||||
with raises(Exception) as exc:
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ DEFAULTS = {
|
|||
"RELAY_CONNECTION_ENFORCE_FIRST_OR_LAST": False,
|
||||
# Max items returned in ConnectionFields / FilterConnectionFields
|
||||
"RELAY_CONNECTION_MAX_LIMIT": 100,
|
||||
"DJANGO_GRAPHENE_CAMELCASE_ERRORS": False,
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
@ -10,3 +12,21 @@ def test_get_model_fields_no_duplication():
|
|||
film_fields = get_model_fields(Film)
|
||||
film_name_set = set([field[0] for field in film_fields])
|
||||
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
|
||||
|
||||
import six
|
||||
from django.db.models import Model
|
||||
from django.utils.functional import SimpleLazyObject
|
||||
|
||||
import graphene
|
||||
from graphene import Field
|
||||
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 .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:
|
||||
from typing import Type
|
||||
|
@ -182,3 +188,12 @@ class DjangoObjectType(ObjectType):
|
|||
class ErrorType(ObjectType):
|
||||
field = 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 (
|
||||
DJANGO_FILTER_INSTALLED,
|
||||
get_reverse_fields,
|
||||
maybe_queryset,
|
||||
camelize,
|
||||
get_model_fields,
|
||||
is_valid_django_model,
|
||||
get_reverse_fields,
|
||||
import_single_dispatch,
|
||||
is_valid_django_model,
|
||||
maybe_queryset,
|
||||
)
|
||||
from .testing import GraphQLTestCase
|
||||
|
||||
__all__ = [
|
||||
"DJANGO_FILTER_INSTALLED",
|
||||
"get_reverse_fields",
|
||||
"maybe_queryset",
|
||||
"get_model_fields",
|
||||
"camelize",
|
||||
"is_valid_django_model",
|
||||
"import_single_dispatch",
|
||||
"GraphQLTestCase",
|
||||
|
|
|
@ -2,7 +2,11 @@ import inspect
|
|||
|
||||
from django.db import models
|
||||
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:
|
||||
import django_filters # noqa
|
||||
|
@ -12,6 +16,28 @@ except ImportError:
|
|||
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):
|
||||
for name, attr in model.__dict__.items():
|
||||
# Don't duplicate any local fields
|
||||
|
|
Loading…
Reference in New Issue
Block a user